summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/aapt/AaptAssets.cpp30
-rw-r--r--tools/aapt/AaptAssets.h2
-rw-r--r--tools/aapt/AaptConfig.cpp31
-rw-r--r--tools/aapt/AaptConfig.h1
-rw-r--r--tools/aapt/Android.mk27
-rw-r--r--tools/aapt/Bundle.h9
-rw-r--r--tools/aapt/Main.cpp11
-rw-r--r--tools/aapt/Resource.cpp2
-rw-r--r--tools/aapt/ResourceTable.cpp48
-rw-r--r--tools/aapt/ResourceTable.h6
-rw-r--r--tools/aapt/SdkConstants.h1
-rw-r--r--tools/aapt/XMLNode.cpp36
-rw-r--r--tools/aapt/tests/AaptConfig_test.cpp24
-rw-r--r--tools/aapt2/Android.mk157
-rw-r--r--tools/aapt2/AppInfo.h37
-rw-r--r--tools/aapt2/BigBuffer.cpp52
-rw-r--r--tools/aapt2/BigBuffer.h159
-rw-r--r--tools/aapt2/BigBuffer_test.cpp98
-rw-r--r--tools/aapt2/BinaryResourceParser.cpp877
-rw-r--r--tools/aapt2/BinaryResourceParser.h159
-rw-r--r--tools/aapt2/BindingXmlPullParser.cpp268
-rw-r--r--tools/aapt2/BindingXmlPullParser.h90
-rw-r--r--tools/aapt2/BindingXmlPullParser_test.cpp110
-rw-r--r--tools/aapt2/Compat_test.cpp33
-rw-r--r--tools/aapt2/ConfigDescription.cpp752
-rw-r--r--tools/aapt2/ConfigDescription.h129
-rw-r--r--tools/aapt2/ConfigDescription_test.cpp82
-rw-r--r--tools/aapt2/Debug.cpp192
-rw-r--r--tools/aapt2/Debug.h35
-rw-r--r--tools/aapt2/Files.cpp188
-rw-r--r--tools/aapt2/Files.h128
-rw-r--r--tools/aapt2/Flag.cpp132
-rw-r--r--tools/aapt2/Flag.h34
-rw-r--r--tools/aapt2/JavaClassGenerator.cpp208
-rw-r--r--tools/aapt2/JavaClassGenerator.h77
-rw-r--r--tools/aapt2/JavaClassGenerator_test.cpp146
-rw-r--r--tools/aapt2/Linker.cpp290
-rw-r--r--tools/aapt2/Linker.h124
-rw-r--r--tools/aapt2/Linker_test.cpp153
-rw-r--r--tools/aapt2/Locale.cpp274
-rw-r--r--tools/aapt2/Locale.h114
-rw-r--r--tools/aapt2/Locale_test.cpp82
-rw-r--r--tools/aapt2/Logger.cpp97
-rw-r--r--tools/aapt2/Logger.h81
-rw-r--r--tools/aapt2/Main.cpp1277
-rw-r--r--tools/aapt2/ManifestMerger.cpp376
-rw-r--r--tools/aapt2/ManifestMerger.h45
-rw-r--r--tools/aapt2/ManifestMerger_test.cpp121
-rw-r--r--tools/aapt2/ManifestParser.cpp84
-rw-r--r--tools/aapt2/ManifestParser.h45
-rw-r--r--tools/aapt2/ManifestParser_test.cpp42
-rw-r--r--tools/aapt2/ManifestValidator.cpp217
-rw-r--r--tools/aapt2/ManifestValidator.h55
-rw-r--r--tools/aapt2/Maybe.h280
-rw-r--r--tools/aapt2/Maybe_test.cpp121
-rw-r--r--tools/aapt2/MockResolver.h93
-rw-r--r--tools/aapt2/NameMangler.h54
-rw-r--r--tools/aapt2/NameMangler_test.cpp45
-rw-r--r--tools/aapt2/Png.cpp1280
-rw-r--r--tools/aapt2/Png.h39
-rw-r--r--tools/aapt2/ProguardRules.cpp240
-rw-r--r--tools/aapt2/ProguardRules.h58
-rw-r--r--tools/aapt2/ResChunkPullParser.cpp68
-rw-r--r--tools/aapt2/ResChunkPullParser.h122
-rw-r--r--tools/aapt2/Resolver.h75
-rw-r--r--tools/aapt2/Resource.cpp90
-rw-r--r--tools/aapt2/Resource.h290
-rw-r--r--tools/aapt2/ResourceParser.cpp1401
-rw-r--r--tools/aapt2/ResourceParser.h195
-rw-r--r--tools/aapt2/ResourceParser_test.cpp492
-rw-r--r--tools/aapt2/ResourceTable.cpp430
-rw-r--r--tools/aapt2/ResourceTable.h277
-rw-r--r--tools/aapt2/ResourceTableResolver.cpp202
-rw-r--r--tools/aapt2/ResourceTableResolver.h70
-rw-r--r--tools/aapt2/ResourceTable_test.cpp228
-rw-r--r--tools/aapt2/ResourceTypeExtensions.h147
-rw-r--r--tools/aapt2/ResourceValues.cpp419
-rw-r--r--tools/aapt2/ResourceValues.h448
-rw-r--r--tools/aapt2/Resource_test.cpp120
-rw-r--r--tools/aapt2/ScopedXmlPullParser.cpp104
-rw-r--r--tools/aapt2/ScopedXmlPullParser.h85
-rw-r--r--tools/aapt2/ScopedXmlPullParser_test.cpp106
-rw-r--r--tools/aapt2/SdkConstants.cpp737
-rw-r--r--tools/aapt2/SdkConstants.h52
-rw-r--r--tools/aapt2/Source.h90
-rw-r--r--tools/aapt2/SourceXmlPullParser.cpp283
-rw-r--r--tools/aapt2/SourceXmlPullParser.h91
-rw-r--r--tools/aapt2/StringPiece.h232
-rw-r--r--tools/aapt2/StringPiece_test.cpp62
-rw-r--r--tools/aapt2/StringPool.cpp394
-rw-r--r--tools/aapt2/StringPool.h223
-rw-r--r--tools/aapt2/StringPool_test.cpp223
-rw-r--r--tools/aapt2/TableFlattener.cpp577
-rw-r--r--tools/aapt2/TableFlattener.h61
-rw-r--r--tools/aapt2/Util.cpp343
-rw-r--r--tools/aapt2/Util.h320
-rw-r--r--tools/aapt2/Util_test.cpp136
-rw-r--r--tools/aapt2/XliffXmlPullParser.cpp113
-rw-r--r--tools/aapt2/XliffXmlPullParser.h64
-rw-r--r--tools/aapt2/XliffXmlPullParser_test.cpp75
-rw-r--r--tools/aapt2/XmlDom.cpp431
-rw-r--r--tools/aapt2/XmlDom.h154
-rw-r--r--tools/aapt2/XmlDom_test.cpp49
-rw-r--r--tools/aapt2/XmlFlattener.cpp574
-rw-r--r--tools/aapt2/XmlFlattener.h69
-rw-r--r--tools/aapt2/XmlFlattener_test.cpp232
-rw-r--r--tools/aapt2/XmlPullParser.h214
-rw-r--r--tools/aapt2/ZipEntry.cpp745
-rw-r--r--tools/aapt2/ZipEntry.h350
-rw-r--r--tools/aapt2/ZipFile.cpp1306
-rw-r--r--tools/aapt2/ZipFile.h278
-rw-r--r--tools/aapt2/data/AndroidManifest.xml7
-rw-r--r--tools/aapt2/data/Makefile83
-rw-r--r--tools/aapt2/data/lib/AndroidManifest.xml6
-rw-r--r--tools/aapt2/data/lib/Makefile81
-rw-r--r--tools/aapt2/data/lib/res/layout/main.xml4
-rw-r--r--tools/aapt2/data/lib/res/raw/hello.txt1
-rw-r--r--tools/aapt2/data/lib/res/values/styles.xml8
-rw-r--r--tools/aapt2/data/res/drawable/icon.pngbin0 -> 2341 bytes
-rw-r--r--tools/aapt2/data/res/drawable/image.xml2
-rw-r--r--tools/aapt2/data/res/drawable/test.9.pngbin0 -> 124 bytes
-rw-r--r--tools/aapt2/data/res/layout/main.xml21
-rw-r--r--tools/aapt2/data/res/values-v4/styles.xml7
-rw-r--r--tools/aapt2/data/res/values/colors.xml6
-rw-r--r--tools/aapt2/data/res/values/styles.xml25
-rw-r--r--tools/aapt2/data/res/values/test.xml13
-rw-r--r--tools/aapt2/data/resources.arscbin0 -> 9059884 bytes
-rw-r--r--tools/aapt2/data/resources_base.arscbin0 -> 4784 bytes
-rw-r--r--tools/aapt2/data/resources_hdpi.arscbin0 -> 936 bytes
-rw-r--r--tools/aapt2/process.dot108
-rw-r--r--tools/aapt2/public_attr_map.py55
-rw-r--r--tools/aapt2/todo.txt29
-rw-r--r--tools/apilint/apilint.py691
-rw-r--r--tools/layoutlib/.idea/libraries/asm_4_0.xml11
-rw-r--r--tools/layoutlib/.idea/libraries/guava.xml11
-rw-r--r--tools/layoutlib/.idea/libraries/icu4j.xml11
-rw-r--r--tools/layoutlib/.idea/libraries/kxml2_2_3_0.xml11
-rw-r--r--tools/layoutlib/.idea/libraries/ninepatch_prebuilt.xml11
-rw-r--r--tools/layoutlib/.idea/libraries/tools_common_prebuilt.xml14
-rw-r--r--tools/layoutlib/.idea/misc.xml10
-rw-r--r--tools/layoutlib/.idea/runConfigurations/Create.xml2
-rw-r--r--tools/layoutlib/Android.mk8
-rw-r--r--tools/layoutlib/bridge/Android.mk2
-rw-r--r--tools/layoutlib/bridge/bridge.iml57
-rw-r--r--tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_100.pngbin19810 -> 0 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_100.pngbin19396 -> 0 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/navigation_bar.xml43
-rw-r--r--tools/layoutlib/bridge/resources/bars/navigation_bar600dp.xml49
-rw-r--r--tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_back.pngbin2980 -> 1524 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_home.pngbin3653 -> 1895 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_recent.pngbin1396 -> 611 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/v21/ldrtl-hdpi/ic_sysbar_back.pngbin3026 -> 1535 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/v21/ldrtl-mdpi/ic_sysbar_back.pngbin1803 -> 965 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/v21/ldrtl-xhdpi/ic_sysbar_back.pngbin4375 -> 2084 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/v21/ldrtl-xxhdpi/ic_sysbar_back.pngbin2062 -> 1358 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_back.pngbin1845 -> 1010 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_home.pngbin2372 -> 1066 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_recent.pngbin1148 -> 562 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_back.pngbin4218 -> 2109 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_home.pngbin5365 -> 2617 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_recent.pngbin1754 -> 733 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_back.pngbin7195 -> 1279 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_home.pngbin8635 -> 2125 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_recent.pngbin2369 -> 573 bytes
-rw-r--r--tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_100.pngbin19839 -> 0 bytes
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/BridgeResources.java180
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java140
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java25
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java13
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java2
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java59
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java78
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java41
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java4
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java19
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java156
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java5
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java2
-rw-r--r--tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java2
-rw-r--r--tools/layoutlib/bridge/src/android/text/GreedyLineBreaker.java192
-rw-r--r--tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java44
-rw-r--r--tools/layoutlib/bridge/src/android/text/LineBreaker.java43
-rw-r--r--tools/layoutlib/bridge/src/android/text/LineWidth.java35
-rw-r--r--tools/layoutlib/bridge/src/android/text/OptimizingLineBreaker.java261
-rw-r--r--tools/layoutlib/bridge/src/android/text/Primitive.java92
-rw-r--r--tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java238
-rw-r--r--tools/layoutlib/bridge/src/android/text/TabStops.java44
-rw-r--r--tools/layoutlib/bridge/src/android/util/Xml_Delegate.java8
-rw-r--r--tools/layoutlib/bridge/src/android/view/BridgeInflater.java21
-rw-r--r--tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java24
-rw-r--r--tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java106
-rw-r--r--tools/layoutlib/bridge/src/android/view/ShadowPainter.java2
-rw-r--r--tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java12
-rw-r--r--tools/layoutlib/bridge/src/android/view/View_Delegate.java13
-rw-r--r--tools/layoutlib/bridge/src/android/view/WindowCallback.java10
-rw-r--r--tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java3
-rw-r--r--tools/layoutlib/bridge/src/com/android/internal/policy/PolicyManager.java72
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java13
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java4
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java246
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java3
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java5
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java6
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java6
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/DesignLibUtil.java65
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java4
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java4
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/BridgeActionBar.java2
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java2
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBar.java4
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java4
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java83
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java9
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java61
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java5
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java3
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java103
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java7
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java2
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java4
-rw-r--r--tools/layoutlib/bridge/src/libcore/icu/DateIntervalFormat_Delegate.java74
-rw-r--r--tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java24
-rw-r--r--tools/layoutlib/bridge/tests/Android.mk1
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle8
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.classbin0 -> 2317 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.classbin1157 -> 1157 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$array.classbin0 -> 519 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.classbin406 -> 456 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 -> 473 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.classbin1978 -> 1978 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$integer.classbin0 -> 492 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.classbin519 -> 554 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.classbin897 -> 1041 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.pngbin11038 -> 10356 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/array_check.pngbin0 -> 9786 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/ArraysCheckWidget.java41
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/array_check.xml6
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml25
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/attrs.xml4
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml3
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java2
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java33
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java2
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java93
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java15
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java21
-rwxr-xr-xtools/layoutlib/bridge/update_nav_icons.sh51
-rw-r--r--tools/layoutlib/create/create.iml15
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java2
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java39
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java22
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java4
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java36
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java54
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java41
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java8
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java67
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java133
-rwxr-xr-xtools/layoutlib/rename_font/build_font.py13
-rwxr-xr-xtools/layoutlib/rename_font/build_font_single.py13
-rw-r--r--tools/obbtool/Android.mk4
-rw-r--r--tools/orientationplot/README.txt2
-rwxr-xr-xtools/orientationplot/orientationplot.py2
-rw-r--r--tools/split-select/Android.mk3
-rw-r--r--tools/split-select/Grouper.cpp9
-rw-r--r--tools/split-select/Grouper_test.cpp9
270 files changed, 27745 insertions, 1017 deletions
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
index 2d35129..d346731 100644
--- a/tools/aapt/AaptAssets.cpp
+++ b/tools/aapt/AaptAssets.cpp
@@ -345,7 +345,8 @@ int AaptLocaleValue::initFromDirName(const Vector<String8>& parts, const int sta
return ++currentIndex;
} else {
- if ((part.length() == 2 || part.length() == 3) && isAlpha(part)) {
+ if ((part.length() == 2 || part.length() == 3)
+ && isAlpha(part) && strcmp("car", part.string())) {
setLanguage(part);
if (++currentIndex == size) {
return size;
@@ -366,33 +367,6 @@ int AaptLocaleValue::initFromDirName(const Vector<String8>& parts, const int sta
return currentIndex;
}
-
-String8 AaptLocaleValue::toDirName() const {
- String8 dirName("");
- if (language[0]) {
- dirName += language;
- } else {
- return dirName;
- }
-
- if (script[0]) {
- dirName += "-s";
- dirName += script;
- }
-
- if (region[0]) {
- dirName += "-r";
- dirName += region;
- }
-
- if (variant[0]) {
- dirName += "-v";
- dirName += variant;
- }
-
- return dirName;
-}
-
void AaptLocaleValue::initFromResTable(const ResTable_config& config) {
config.unpackLanguage(language);
config.unpackRegion(region);
diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h
index 7ae5368..4fdc964 100644
--- a/tools/aapt/AaptAssets.h
+++ b/tools/aapt/AaptAssets.h
@@ -78,8 +78,6 @@ struct AaptLocaleValue {
void writeTo(ResTable_config* out) const;
- String8 toDirName() const;
-
int compare(const AaptLocaleValue& other) const {
return memcmp(this, &other, sizeof(AaptLocaleValue));
}
diff --git a/tools/aapt/AaptConfig.cpp b/tools/aapt/AaptConfig.cpp
index ede9e99..b12867a 100644
--- a/tools/aapt/AaptConfig.cpp
+++ b/tools/aapt/AaptConfig.cpp
@@ -123,6 +123,14 @@ bool parse(const String8& str, ConfigDescription* out) {
part = parts[index].string();
}
+ if (parseScreenRound(part, &config)) {
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index].string();
+ }
+
if (parseOrientation(part, &config)) {
index++;
if (index == N) {
@@ -241,7 +249,9 @@ void applyVersionForCompatibility(ConfigDescription* config) {
}
uint16_t minSdk = 0;
- if (config->density == ResTable_config::DENSITY_ANY) {
+ if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) {
+ minSdk = SDK_MNC;
+ } 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
@@ -395,7 +405,26 @@ bool parseScreenLayoutLong(const char* name, ResTable_config* out) {
| ResTable_config::SCREENLONG_NO;
return true;
}
+ return false;
+}
+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;
}
diff --git a/tools/aapt/AaptConfig.h b/tools/aapt/AaptConfig.h
index f73a508..04c763f 100644
--- a/tools/aapt/AaptConfig.h
+++ b/tools/aapt/AaptConfig.h
@@ -60,6 +60,7 @@ bool parseScreenWidthDp(const char* str, android::ResTable_config* out = NULL);
bool parseScreenHeightDp(const char* str, android::ResTable_config* out = NULL);
bool parseScreenLayoutSize(const char* str, android::ResTable_config* out = NULL);
bool parseScreenLayoutLong(const char* str, android::ResTable_config* out = NULL);
+bool parseScreenRound(const char* name, android::ResTable_config* out = NULL);
bool parseOrientation(const char* str, android::ResTable_config* out = NULL);
bool parseUiModeType(const char* str, android::ResTable_config* out = NULL);
bool parseUiModeNight(const char* str, android::ResTable_config* out = NULL);
diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk
index 9956bd7..bbe6860 100644
--- a/tools/aapt/Android.mk
+++ b/tools/aapt/Android.mk
@@ -64,7 +64,8 @@ aaptHostStaticLibs := \
libutils \
libcutils \
libexpat \
- libziparchive-host
+ libziparchive-host \
+ libbase
aaptCFlags := -DAAPT_VERSION=\"$(BUILD_NUMBER)\"
aaptCFlags += -Wall -Werror
@@ -131,28 +132,4 @@ LOCAL_STATIC_LIBRARIES += libaapt $(aaptHostStaticLibs)
include $(BUILD_HOST_NATIVE_TEST)
-# ==========================================================
-# Build the device executable: aapt
-# ==========================================================
-ifneq ($(SDK_ONLY),true)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := aapt
-LOCAL_CFLAGS += $(aaptCFlags)
-LOCAL_SRC_FILES := $(aaptSources) $(aaptMain)
-LOCAL_C_INCLUDES += $(aaptCIncludes)
-LOCAL_SHARED_LIBRARIES := \
- libandroidfw \
- libutils \
- libcutils \
- libpng \
- liblog \
- libz
-LOCAL_STATIC_LIBRARIES := \
- libexpat_static
-
-include $(BUILD_EXECUTABLE)
-
-endif # Not SDK_ONLY
-
endif # No TARGET_BUILD_APPS or TARGET_BUILD_PDK
diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h
index e7cde74..cbe7c5d 100644
--- a/tools/aapt/Bundle.h
+++ b/tools/aapt/Bundle.h
@@ -54,13 +54,14 @@ public:
mWantUTF16(false), mValues(false), mIncludeMetaData(false),
mCompressionMethod(0), mJunkPath(false), mOutputAPKFile(NULL),
mManifestPackageNameOverride(NULL), mInstrumentationPackageNameOverride(NULL),
- mAutoAddOverlay(false), mGenDependencies(false),
+ mAutoAddOverlay(false), mGenDependencies(false), mNoVersionVectors(false),
mCrunchedOutputDir(NULL), mProguardFile(NULL),
mAndroidManifestFile(NULL), mPublicOutputFile(NULL),
mRClassDir(NULL), mResourceIntermediatesDir(NULL), mManifestMinSdkVersion(NULL),
mMinSdkVersion(NULL), mTargetSdkVersion(NULL), mMaxSdkVersion(NULL),
mVersionCode(NULL), mVersionName(NULL), mReplaceVersion(false), mCustomPackage(NULL),
mExtraPackages(NULL), mMaxResVersion(NULL), mDebugMode(false), mNonConstantId(false),
+ mSkipSymbolsWithoutDefaultLocalization(false),
mProduct(NULL), mUseCrunchCache(false), mErrorOnFailedInsert(false),
mErrorOnMissingConfigEntry(false), mOutputTextSymbols(NULL),
mSingleCrunchInputFile(NULL), mSingleCrunchOutputFile(NULL),
@@ -191,6 +192,8 @@ public:
void setDebugMode(bool val) { mDebugMode = val; }
bool getNonConstantId() const { return mNonConstantId; }
void setNonConstantId(bool val) { mNonConstantId = val; }
+ bool getSkipSymbolsWithoutDefaultLocalization() const { return mSkipSymbolsWithoutDefaultLocalization; }
+ void setSkipSymbolsWithoutDefaultLocalization(bool val) { mSkipSymbolsWithoutDefaultLocalization = val; }
const char* getProduct() const { return mProduct; }
void setProduct(const char * val) { mProduct = val; }
void setUseCrunchCache(bool val) { mUseCrunchCache = val; }
@@ -203,6 +206,8 @@ public:
void setSingleCrunchOutputFile(const char* val) { mSingleCrunchOutputFile = val; }
bool getBuildSharedLibrary() const { return mBuildSharedLibrary; }
void setBuildSharedLibrary(bool val) { mBuildSharedLibrary = val; }
+ void setNoVersionVectors(bool val) { mNoVersionVectors = val; }
+ bool getNoVersionVectors() const { return mNoVersionVectors; }
/*
* Set and get the file specification.
@@ -282,6 +287,7 @@ private:
const char* mInstrumentationPackageNameOverride;
bool mAutoAddOverlay;
bool mGenDependencies;
+ bool mNoVersionVectors;
const char* mCrunchedOutputDir;
const char* mProguardFile;
const char* mAndroidManifestFile;
@@ -312,6 +318,7 @@ private:
const char* mMaxResVersion;
bool mDebugMode;
bool mNonConstantId;
+ bool mSkipSymbolsWithoutDefaultLocalization;
const char* mProduct;
bool mUseCrunchCache;
bool mErrorOnFailedInsert;
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
index 8b416aa..f832c60 100644
--- a/tools/aapt/Main.cpp
+++ b/tools/aapt/Main.cpp
@@ -211,7 +211,12 @@ void usage(void)
" specified folder.\n"
" --ignore-assets\n"
" Assets to be ignored. Default pattern is:\n"
- " %s\n",
+ " %s\n"
+ " --skip-symbols-without-default-localization\n"
+ " Prevents symbols from being generated for strings that do not have a default\n"
+ " localization\n"
+ " --no-version-vectors\n"
+ " Do not automatically generate versioned copies of vector XML resources.\n",
gDefaultIgnoreAssets);
}
@@ -657,6 +662,8 @@ int main(int argc, char* const argv[])
bundle.setProduct(argv[0]);
} else if (strcmp(cp, "-non-constant-id") == 0) {
bundle.setNonConstantId(true);
+ } else if (strcmp(cp, "-skip-symbols-without-default-localization") == 0) {
+ bundle.setSkipSymbolsWithoutDefaultLocalization(true);
} else if (strcmp(cp, "-shared-lib") == 0) {
bundle.setNonConstantId(true);
bundle.setBuildSharedLibrary(true);
@@ -673,6 +680,8 @@ int main(int argc, char* const argv[])
gUserIgnoreAssets = argv[0];
} else if (strcmp(cp, "-pseudo-localize") == 0) {
bundle.setPseudolocalize(PSEUDO_ACCENTED | PSEUDO_BIDI);
+ } else if (strcmp(cp, "-no-version-vectors") == 0) {
+ bundle.setNoVersionVectors(true);
} else {
fprintf(stderr, "ERROR: Unknown option '-%s'\n", cp);
wantUsage = true;
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index beb94fd..5d20815 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1604,7 +1604,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil
if (table.hasResources()) {
sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
- err = table.addSymbols(symbols);
+ err = table.addSymbols(symbols, bundle->getSkipSymbolsWithoutDefaultLocalization());
if (err < NO_ERROR) {
return err;
}
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index c5fccbf..e64fdf7 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -913,6 +913,7 @@ status_t compileResourceFile(Bundle* bundle,
if (code == ResXMLTree::START_TAG) {
const String16* curTag = NULL;
String16 curType;
+ String16 curName;
int32_t curFormat = ResTable_map::TYPE_ANY;
bool curIsBag = false;
bool curIsBagReplaceOnOverwrite = false;
@@ -1321,6 +1322,10 @@ status_t compileResourceFile(Bundle* bundle,
ssize_t attri = block.indexOfAttribute(NULL, "type");
if (attri >= 0) {
curType = String16(block.getAttributeStringValue(attri, &len));
+ ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
+ if (nameIdx >= 0) {
+ curName = String16(block.getAttributeStringValue(nameIdx, &len));
+ }
ssize_t formatIdx = block.indexOfAttribute(NULL, "format");
if (formatIdx >= 0) {
String16 formatStr = String16(block.getAttributeStringValue(
@@ -1363,6 +1368,9 @@ status_t compileResourceFile(Bundle* bundle,
}
if (name.size() > 0) {
+ if (locale.size() == 0) {
+ outTable->addDefaultLocalization(name);
+ }
if (translatable == false16) {
curIsFormatted = false;
// Untranslatable strings must only exist in the default [empty] locale
@@ -1658,6 +1666,9 @@ status_t compileResourceFile(Bundle* bundle,
hasErrors = localHasErrors = true;
}
else if (err == NO_ERROR) {
+ if (curType == string16 && !curParams.language[0] && !curParams.country[0]) {
+ outTable->addDefaultLocalization(curName);
+ }
if (curIsPseudolocalizable && localeIsDefined(curParams)
&& bundle->getPseudolocalize() > 0) {
// pseudolocalize here
@@ -2622,8 +2633,11 @@ status_t ResourceTable::assignResourceIds()
return firstError;
}
-status_t ResourceTable::addSymbols(const sp<AaptSymbols>& outSymbols) {
+status_t ResourceTable::addSymbols(const sp<AaptSymbols>& outSymbols,
+ bool skipSymbolsWithoutDefaultLocalization) {
const size_t N = mOrderedPackages.size();
+ const String8 defaultLocale;
+ const String16 stringType("string");
size_t pi;
for (pi=0; pi<N; pi++) {
@@ -2664,6 +2678,19 @@ status_t ResourceTable::addSymbols(const sp<AaptSymbols>& outSymbols) {
return UNKNOWN_ERROR;
}
if (Res_GETPACKAGE(rid) + 1 == p->getAssignedId()) {
+
+ if (skipSymbolsWithoutDefaultLocalization &&
+ t->getName() == stringType) {
+
+ // Don't generate symbols for strings without a default localization.
+ if (mHasDefaultLocalization.find(c->getName())
+ == mHasDefaultLocalization.end()) {
+ // printf("Skip symbol [%08x] %s\n", rid,
+ // String8(c->getName()).string());
+ continue;
+ }
+ }
+
typeSymbols->addSymbol(String8(c->getName()), rid, c->getPos());
String16 comment(c->getComment());
@@ -2686,6 +2713,12 @@ ResourceTable::addLocalization(const String16& name, const String8& locale, cons
mLocalizations[name][locale] = src;
}
+void
+ResourceTable::addDefaultLocalization(const String16& name)
+{
+ mHasDefaultLocalization.insert(name);
+}
+
/*!
* Flag various sorts of localization problems. '+' indicates checks already implemented;
@@ -4611,6 +4644,9 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle,
const String16& resourceName,
const sp<AaptFile>& target,
const sp<XMLNode>& root) {
+ const String16 vector16("vector");
+ const String16 animatedVector16("animated-vector");
+
const int minSdk = getMinSdkVersion(bundle);
if (minSdk >= SDK_LOLLIPOP_MR1) {
// Lollipop MR1 and up handles public attributes differently, no
@@ -4620,8 +4656,8 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle,
const ConfigDescription config(target->getGroupEntry().toParams());
if (target->getResourceType() == "" || config.sdkVersion >= SDK_LOLLIPOP_MR1) {
- // Skip resources that have no type (AndroidManifest.xml) or are already version qualified with v21
- // or higher.
+ // Skip resources that have no type (AndroidManifest.xml) or are already version qualified
+ // with v21 or higher.
return NO_ERROR;
}
@@ -4635,6 +4671,12 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle,
sp<XMLNode> node = nodesToVisit.top();
nodesToVisit.pop();
+ if (bundle->getNoVersionVectors() && (node->getElementName() == vector16 ||
+ node->getElementName() == animatedVector16)) {
+ // We were told not to version vector tags, so skip the children here.
+ continue;
+ }
+
const Vector<XMLNode::attribute_entry>& attrs = node->getAttributes();
for (size_t i = 0; i < attrs.size(); i++) {
const XMLNode::attribute_entry& attr = attrs[i];
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index 9644224..2c1bec1 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -235,8 +235,10 @@ public:
const ConfigDescription* config = NULL);
status_t assignResourceIds();
- status_t addSymbols(const sp<AaptSymbols>& outSymbols = NULL);
+ status_t addSymbols(const sp<AaptSymbols>& outSymbols = NULL,
+ bool skipSymbolsWithoutDefaultLocalization = false);
void addLocalization(const String16& name, const String8& locale, const SourcePos& src);
+ void addDefaultLocalization(const String16& name);
status_t validateLocalizations(void);
status_t flatten(Bundle* bundle, const sp<const ResourceFilter>& filter,
@@ -588,6 +590,8 @@ private:
// key = string resource name, value = set of locales in which that name is defined
std::map<String16, std::map<String8, SourcePos>> mLocalizations;
+ // set of string resources names that have a default localization
+ std::set<String16> mHasDefaultLocalization;
std::queue<CompileResourceWorkItem> mWorkQueue;
};
diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h
index 4e0fe10..16e622a 100644
--- a/tools/aapt/SdkConstants.h
+++ b/tools/aapt/SdkConstants.h
@@ -38,6 +38,7 @@ enum {
SDK_KITKAT_WATCH = 20,
SDK_LOLLIPOP = 21,
SDK_LOLLIPOP_MR1 = 22,
+ SDK_MNC = 23,
};
#endif // H_AAPT_SDK_CONSTANTS
diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp
index 9033cf7..6902a30 100644
--- a/tools/aapt/XMLNode.cpp
+++ b/tools/aapt/XMLNode.cpp
@@ -466,7 +466,7 @@ void printXMLBlock(ResXMLTree* block)
block->restart();
Vector<namespace_entry> namespaces;
-
+
ResXMLTree::event_code_t code;
int depth = 0;
while ((code=block->next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
@@ -520,7 +520,12 @@ void printXMLBlock(ResXMLTree* block)
printf("\n");
}
} else if (code == ResXMLTree::END_TAG) {
- depth--;
+ // Invalid tag nesting can be misused to break the parsing
+ // code below. Break if detected.
+ if (--depth < 0) {
+ printf("***BAD DEPTH in XMLBlock: %d\n", depth);
+ break;
+ }
} else if (code == ResXMLTree::START_NAMESPACE) {
namespace_entry ns;
size_t len;
@@ -536,7 +541,10 @@ void printXMLBlock(ResXMLTree* block)
ns.uri.string());
depth++;
} else if (code == ResXMLTree::END_NAMESPACE) {
- depth--;
+ if (--depth < 0) {
+ printf("***BAD DEPTH in XMLBlock: %d\n", depth);
+ break;
+ }
const namespace_entry& ns = namespaces.top();
size_t len;
const char16_t* prefix16 = block->getNamespacePrefix(&len);
@@ -714,7 +722,7 @@ const String8& XMLNode::getFilename() const
{
return mFilename;
}
-
+
const Vector<XMLNode::attribute_entry>&
XMLNode::getAttributes() const
{
@@ -730,7 +738,7 @@ const XMLNode::attribute_entry* XMLNode::getAttribute(const String16& ns,
return &ae;
}
}
-
+
return NULL;
}
@@ -774,14 +782,14 @@ sp<XMLNode> XMLNode::searchElement(const String16& tagNamespace, const String16&
&& mElementName == tagName) {
return this;
}
-
+
for (size_t i=0; i<mChildren.size(); i++) {
sp<XMLNode> found = mChildren.itemAt(i)->searchElement(tagNamespace, tagName);
if (found != NULL) {
return found;
}
}
-
+
return NULL;
}
@@ -795,7 +803,7 @@ sp<XMLNode> XMLNode::getChildElement(const String16& tagNamespace, const String1
return child;
}
}
-
+
return NULL;
}
@@ -977,7 +985,7 @@ status_t XMLNode::parseValues(const sp<AaptAssets>& assets,
ResourceTable* table)
{
bool hasErrors = false;
-
+
if (getType() == TYPE_ELEMENT) {
const size_t N = mAttributes.size();
String16 defPackage(assets->getPackage());
@@ -1013,7 +1021,7 @@ status_t XMLNode::assignResourceIds(const sp<AaptAssets>& assets,
const ResourceTable* table)
{
bool hasErrors = false;
-
+
if (getType() == TYPE_ELEMENT) {
String16 attr("attr");
const char* errorMsg;
@@ -1093,7 +1101,7 @@ status_t XMLNode::flatten(const sp<AaptFile>& dest,
{
StringPool strings(mUTF8);
Vector<uint32_t> resids;
-
+
// First collect just the strings for attribute names that have a
// resource ID assigned to them. This ensures that the resource ID
// array is compact, and makes it easier to deal with attribute names
@@ -1141,7 +1149,7 @@ status_t XMLNode::flatten(const sp<AaptFile>& dest,
dest->getSize(), (stringPool->getSize()*100)/dest->getSize(),
dest->getPath().string());
}
-
+
return NO_ERROR;
}
@@ -1217,7 +1225,7 @@ XMLNode::startNamespace(void *userData, const char *prefix, const char *uri)
printf("Start Namespace: %s %s\n", prefix, uri);
}
ParseState* st = (ParseState*)userData;
- sp<XMLNode> node = XMLNode::newNamespace(st->filename,
+ sp<XMLNode> node = XMLNode::newNamespace(st->filename,
String16(prefix != NULL ? prefix : ""), String16(uri));
node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser));
if (st->stack.size() > 0) {
@@ -1338,7 +1346,7 @@ status_t XMLNode::collect_strings(StringPool* dest, Vector<uint32_t>* outResIds,
bool stripComments, bool stripRawValues) const
{
collect_attr_strings(dest, outResIds, true);
-
+
int i;
if (RESOURCES_TOOLS_NAMESPACE != mNamespaceUri) {
if (mNamespacePrefix.size() > 0) {
diff --git a/tools/aapt/tests/AaptConfig_test.cpp b/tools/aapt/tests/AaptConfig_test.cpp
index e795d81..8bb38ba 100644
--- a/tools/aapt/tests/AaptConfig_test.cpp
+++ b/tools/aapt/tests/AaptConfig_test.cpp
@@ -19,6 +19,7 @@
#include "AaptConfig.h"
#include "ConfigDescription.h"
+#include "SdkConstants.h"
#include "TestHelper.h"
using android::String8;
@@ -65,7 +66,7 @@ TEST(AaptConfigTest, ParseBasicQualifiers) {
TEST(AaptConfigTest, ParseLocales) {
ConfigDescription config;
EXPECT_TRUE(TestParse("en-rUS", &config));
- EXPECT_EQ(String8("en-US"), config.toString());
+ EXPECT_EQ(String8("en-rUS"), config.toString());
}
TEST(AaptConfigTest, ParseQualifierAddedInApi13) {
@@ -76,3 +77,24 @@ TEST(AaptConfigTest, ParseQualifierAddedInApi13) {
EXPECT_TRUE(TestParse("sw600dp-v8", &config));
EXPECT_EQ(String8("sw600dp-v13"), config.toString());
}
+
+TEST(AaptConfigTest, TestParsingOfCarAttribute) {
+ ConfigDescription config;
+ EXPECT_TRUE(TestParse("car", &config));
+ EXPECT_EQ(android::ResTable_config::UI_MODE_TYPE_CAR, config.uiMode);
+}
+
+TEST(AaptConfigTest, 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_MNC, config.sdkVersion);
+ EXPECT_EQ(String8("round-v23"), config.toString());
+
+ EXPECT_TRUE(TestParse("notround", &config));
+ EXPECT_EQ(android::ResTable_config::SCREENROUND_NO,
+ config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND);
+ EXPECT_EQ(SDK_MNC, config.sdkVersion);
+ EXPECT_EQ(String8("notround-v23"), config.toString());
+}
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
new file mode 100644
index 0000000..10f8150
--- /dev/null
+++ b/tools/aapt2/Android.mk
@@ -0,0 +1,157 @@
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# This tool is prebuilt if we're doing an app-only build.
+ifeq ($(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)),)
+
+# ==========================================================
+# Setup some common variables for the different build
+# targets here.
+# ==========================================================
+LOCAL_PATH:= $(call my-dir)
+
+main := Main.cpp
+sources := \
+ BigBuffer.cpp \
+ BinaryResourceParser.cpp \
+ BindingXmlPullParser.cpp \
+ ConfigDescription.cpp \
+ Debug.cpp \
+ Files.cpp \
+ Flag.cpp \
+ JavaClassGenerator.cpp \
+ Linker.cpp \
+ Locale.cpp \
+ Logger.cpp \
+ ManifestMerger.cpp \
+ ManifestParser.cpp \
+ ManifestValidator.cpp \
+ Png.cpp \
+ ProguardRules.cpp \
+ ResChunkPullParser.cpp \
+ Resource.cpp \
+ ResourceParser.cpp \
+ ResourceTable.cpp \
+ ResourceTableResolver.cpp \
+ ResourceValues.cpp \
+ SdkConstants.cpp \
+ StringPool.cpp \
+ TableFlattener.cpp \
+ Util.cpp \
+ ScopedXmlPullParser.cpp \
+ SourceXmlPullParser.cpp \
+ XliffXmlPullParser.cpp \
+ XmlDom.cpp \
+ XmlFlattener.cpp \
+ ZipEntry.cpp \
+ ZipFile.cpp
+
+testSources := \
+ BigBuffer_test.cpp \
+ BindingXmlPullParser_test.cpp \
+ Compat_test.cpp \
+ ConfigDescription_test.cpp \
+ JavaClassGenerator_test.cpp \
+ Linker_test.cpp \
+ Locale_test.cpp \
+ ManifestMerger_test.cpp \
+ ManifestParser_test.cpp \
+ Maybe_test.cpp \
+ NameMangler_test.cpp \
+ ResourceParser_test.cpp \
+ Resource_test.cpp \
+ ResourceTable_test.cpp \
+ ScopedXmlPullParser_test.cpp \
+ StringPiece_test.cpp \
+ StringPool_test.cpp \
+ Util_test.cpp \
+ XliffXmlPullParser_test.cpp \
+ XmlDom_test.cpp \
+ XmlFlattener_test.cpp
+
+cIncludes := \
+ external/libpng \
+ external/libz
+
+hostLdLibs :=
+
+hostStaticLibs := \
+ libandroidfw \
+ libutils \
+ liblog \
+ libcutils \
+ libexpat \
+ libziparchive-host \
+ libpng \
+ libbase
+
+ifneq ($(strip $(USE_MINGW)),)
+ hostStaticLibs += libz
+else
+ hostLdLibs += -lz
+endif
+
+cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG
+cppFlags := -std=c++11 -Wno-missing-field-initializers -Wno-unused-private-field
+
+# ==========================================================
+# Build the host static library: libaapt2
+# ==========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libaapt2
+
+LOCAL_SRC_FILES := $(sources)
+LOCAL_C_INCLUDES += $(cIncludes)
+LOCAL_CFLAGS += $(cFlags)
+LOCAL_CPPFLAGS += $(cppFlags)
+
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+
+# ==========================================================
+# Build the host tests: libaapt2_tests
+# ==========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libaapt2_tests
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(testSources)
+
+LOCAL_C_INCLUDES += $(cIncludes)
+LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs)
+LOCAL_LDLIBS += $(hostLdLibs)
+LOCAL_CFLAGS += $(cFlags)
+LOCAL_CPPFLAGS += $(cppFlags)
+
+include $(BUILD_HOST_NATIVE_TEST)
+
+# ==========================================================
+# Build the host executable: aapt2
+# ==========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := aapt2
+
+LOCAL_SRC_FILES := $(main)
+
+LOCAL_C_INCLUDES += $(cIncludes)
+LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs)
+LOCAL_LDLIBS += $(hostLdLibs)
+LOCAL_CFLAGS += $(cFlags)
+LOCAL_CPPFLAGS += $(cppFlags)
+
+include $(BUILD_HOST_EXECUTABLE)
+
+endif # No TARGET_BUILD_APPS or TARGET_BUILD_PDK
diff --git a/tools/aapt2/AppInfo.h b/tools/aapt2/AppInfo.h
new file mode 100644
index 0000000..30047f7
--- /dev/null
+++ b/tools/aapt2/AppInfo.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_APP_INFO_H
+#define AAPT_APP_INFO_H
+
+#include <string>
+
+namespace aapt {
+
+/**
+ * Holds basic information about the app being built. Most of this information
+ * will come from the app's AndroidManifest.
+ */
+struct AppInfo {
+ /**
+ * App's package name.
+ */
+ std::u16string package;
+};
+
+} // namespace aapt
+
+#endif // AAPT_APP_INFO_H
diff --git a/tools/aapt2/BigBuffer.cpp b/tools/aapt2/BigBuffer.cpp
new file mode 100644
index 0000000..8f57172
--- /dev/null
+++ b/tools/aapt2/BigBuffer.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BigBuffer.h"
+
+#include <algorithm>
+#include <memory>
+#include <vector>
+
+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;
+ }
+ }
+
+ const size_t actualSize = std::max(mBlockSize, size);
+
+ Block block = {};
+
+ // Zero-allocate the block's buffer.
+ block.buffer = std::unique_ptr<uint8_t[]>(new uint8_t[actualSize]());
+ assert(block.buffer);
+
+ block.size = size;
+ block.mBlockSize = actualSize;
+
+ mBlocks.push_back(std::move(block));
+ mSize += size;
+ return mBlocks.back().buffer.get();
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/BigBuffer.h b/tools/aapt2/BigBuffer.h
new file mode 100644
index 0000000..8b6569c
--- /dev/null
+++ b/tools/aapt2/BigBuffer.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_BIG_BUFFER_H
+#define AAPT_BIG_BUFFER_H
+
+#include <cassert>
+#include <cstring>
+#include <memory>
+#include <vector>
+
+namespace aapt {
+
+/**
+ * Inspired by protobuf's ZeroCopyOutputStream, offers blocks of memory
+ * in which to write without knowing the full size of the entire payload.
+ * This is essentially a list of memory blocks. As one fills up, another
+ * 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);
+
+ /**
+ * 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();
+
+ const_iterator begin() const;
+ const_iterator end() const;
+
+private:
+ /**
+ * Returns a pointer to a buffer of the requested size.
+ * The buffer is zero-initialized.
+ */
+ void* nextBlockImpl(size_t size);
+
+ size_t mBlockSize;
+ size_t mSize;
+ std::vector<Block> mBlocks;
+};
+
+inline BigBuffer::BigBuffer(size_t blockSize) : mBlockSize(blockSize), mSize(0) {
+}
+
+inline BigBuffer::BigBuffer(BigBuffer&& rhs) :
+ mBlockSize(rhs.mBlockSize), mSize(rhs.mSize), mBlocks(std::move(rhs.mBlocks)) {
+}
+
+inline size_t BigBuffer::size() const {
+ return mSize;
+}
+
+template <typename T>
+inline T* BigBuffer::nextBlock(size_t count) {
+ assert(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::pad(size_t bytes) {
+ nextBlock<char>(bytes);
+}
+
+inline void BigBuffer::align4() {
+ const size_t unaligned = mSize % 4;
+ if (unaligned != 0) {
+ pad(4 - unaligned);
+ }
+}
+
+inline BigBuffer::const_iterator BigBuffer::begin() const {
+ return mBlocks.begin();
+}
+
+inline BigBuffer::const_iterator BigBuffer::end() const {
+ return mBlocks.end();
+}
+
+} // namespace aapt
+
+#endif // AAPT_BIG_BUFFER_H
diff --git a/tools/aapt2/BigBuffer_test.cpp b/tools/aapt2/BigBuffer_test.cpp
new file mode 100644
index 0000000..01ee8d7
--- /dev/null
+++ b/tools/aapt2/BigBuffer_test.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BigBuffer.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(BigBufferTest, AllocateSingleBlock) {
+ BigBuffer buffer(4);
+
+ EXPECT_NE(nullptr, buffer.nextBlock<char>(2));
+ EXPECT_EQ(2u, buffer.size());
+}
+
+TEST(BigBufferTest, ReturnSameBlockIfNextAllocationFits) {
+ BigBuffer buffer(16);
+
+ char* b1 = buffer.nextBlock<char>(8);
+ EXPECT_NE(nullptr, b1);
+
+ char* b2 = buffer.nextBlock<char>(4);
+ EXPECT_NE(nullptr, b2);
+
+ EXPECT_EQ(b1 + 8, b2);
+}
+
+TEST(BigBufferTest, AllocateExactSizeBlockIfLargerThanBlockSize) {
+ BigBuffer buffer(16);
+
+ EXPECT_NE(nullptr, buffer.nextBlock<char>(32));
+ EXPECT_EQ(32u, buffer.size());
+}
+
+TEST(BigBufferTest, AppendAndMoveBlock) {
+ BigBuffer buffer(16);
+
+ 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;
+
+ buffer.appendBuffer(std::move(buffer2));
+ EXPECT_EQ(0u, buffer2.size());
+ EXPECT_EQ(buffer2.begin(), buffer2.end());
+ }
+
+ 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;
+
+ 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());
+}
+
+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());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/BinaryResourceParser.cpp b/tools/aapt2/BinaryResourceParser.cpp
new file mode 100644
index 0000000..3559f43
--- /dev/null
+++ b/tools/aapt2/BinaryResourceParser.cpp
@@ -0,0 +1,877 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BinaryResourceParser.h"
+#include "Logger.h"
+#include "ResChunkPullParser.h"
+#include "Resolver.h"
+#include "ResourceParser.h"
+#include "ResourceTable.h"
+#include "ResourceTypeExtensions.h"
+#include "ResourceValues.h"
+#include "Source.h"
+#include "Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <androidfw/TypeWrappers.h>
+#include <map>
+#include <string>
+
+namespace aapt {
+
+using namespace android;
+
+/*
+ * Visitor that converts a reference's resource ID to a resource name,
+ * given a mapping from resource ID to resource name.
+ */
+struct ReferenceIdToNameVisitor : ValueVisitor {
+ ReferenceIdToNameVisitor(const std::shared_ptr<IResolver>& resolver,
+ std::map<ResourceId, ResourceName>* cache) :
+ mResolver(resolver), mCache(cache) {
+ }
+
+ void visit(Reference& reference, ValueVisitorArgs&) override {
+ idToName(reference);
+ }
+
+ void visit(Attribute& attr, ValueVisitorArgs&) override {
+ for (auto& entry : attr.symbols) {
+ idToName(entry.symbol);
+ }
+ }
+
+ void visit(Style& style, ValueVisitorArgs&) override {
+ if (style.parent.id.isValid()) {
+ idToName(style.parent);
+ }
+
+ for (auto& entry : style.entries) {
+ idToName(entry.key);
+ entry.value->accept(*this, {});
+ }
+ }
+
+ void visit(Styleable& styleable, ValueVisitorArgs&) override {
+ for (auto& attr : styleable.entries) {
+ idToName(attr);
+ }
+ }
+
+ void visit(Array& array, ValueVisitorArgs&) override {
+ for (auto& item : array.items) {
+ item->accept(*this, {});
+ }
+ }
+
+ void visit(Plural& plural, ValueVisitorArgs&) override {
+ for (auto& item : plural.values) {
+ if (item) {
+ item->accept(*this, {});
+ }
+ }
+ }
+
+private:
+ void idToName(Reference& reference) {
+ if (!reference.id.isValid()) {
+ return;
+ }
+
+ auto cacheIter = mCache->find(reference.id);
+ if (cacheIter != mCache->end()) {
+ reference.name = cacheIter->second;
+ reference.id = 0;
+ } else {
+ Maybe<ResourceName> result = mResolver->findName(reference.id);
+ if (result) {
+ reference.name = result.value();
+
+ // Add to cache.
+ mCache->insert({reference.id, reference.name});
+
+ reference.id = 0;
+ }
+ }
+ }
+
+ std::shared_ptr<IResolver> mResolver;
+ std::map<ResourceId, ResourceName>* mCache;
+};
+
+
+BinaryResourceParser::BinaryResourceParser(const std::shared_ptr<ResourceTable>& table,
+ const std::shared_ptr<IResolver>& resolver,
+ const Source& source,
+ const void* data,
+ size_t len) :
+ mTable(table), mResolver(resolver), 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) {
+ Logger::warn(mSource)
+ << "unknown chunk of type '"
+ << parser.getChunk()->type
+ << "'."
+ << std::endl;
+ continue;
+ }
+
+ error |= !parseTable(parser.getChunk());
+ }
+
+ if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
+ Logger::error(mSource)
+ << "bad document: "
+ << parser.getLastError()
+ << "."
+ << std::endl;
+ return false;
+ }
+ return !error;
+}
+
+bool BinaryResourceParser::getSymbol(const void* data, ResourceNameRef* outSymbol) {
+ if (!mSymbolEntries || mSymbolEntryCount == 0) {
+ return false;
+ }
+
+ if (reinterpret_cast<uintptr_t>(data) < reinterpret_cast<uintptr_t>(mData)) {
+ return false;
+ }
+
+ // We only support 32 bit offsets right now.
+ const uintptr_t offset = reinterpret_cast<uintptr_t>(data) -
+ reinterpret_cast<uintptr_t>(mData);
+ if (offset > std::numeric_limits<uint32_t>::max()) {
+ return false;
+ }
+
+ for (size_t i = 0; i < mSymbolEntryCount; i++) {
+ if (mSymbolEntries[i].offset == offset) {
+ // This offset is a symbol!
+ const StringPiece16 str = util::getString(mSymbolPool,
+ mSymbolEntries[i].stringIndex);
+ StringPiece16 typeStr;
+ ResourceParser::extractResourceName(str, &outSymbol->package, &typeStr,
+ &outSymbol->entry);
+ const ResourceType* type = parseResourceType(typeStr);
+ if (!type) {
+ return false;
+ }
+ outSymbol->type = *type;
+
+ // Since we scan the symbol table in order, we can start looking for the
+ // next symbol from this point.
+ mSymbolEntryCount -= i + 1;
+ mSymbolEntries += i + 1;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool BinaryResourceParser::parseSymbolTable(const ResChunk_header* chunk) {
+ const SymbolTable_header* symbolTableHeader = convertTo<SymbolTable_header>(chunk);
+ if (!symbolTableHeader) {
+ Logger::error(mSource)
+ << "could not parse chunk as SymbolTable_header."
+ << std::endl;
+ return false;
+ }
+
+ const size_t entrySizeBytes = symbolTableHeader->count * sizeof(SymbolTable_entry);
+ if (entrySizeBytes > getChunkDataLen(symbolTableHeader->header)) {
+ Logger::error(mSource)
+ << "entries extend beyond chunk."
+ << std::endl;
+ return false;
+ }
+
+ mSymbolEntries = reinterpret_cast<const SymbolTable_entry*>(
+ getChunkData(symbolTableHeader->header));
+ mSymbolEntryCount = symbolTableHeader->count;
+
+ ResChunkPullParser parser(getChunkData(symbolTableHeader->header) + entrySizeBytes,
+ getChunkDataLen(symbolTableHeader->header) - entrySizeBytes);
+ if (!ResChunkPullParser::isGoodEvent(parser.next())) {
+ Logger::error(mSource)
+ << "failed to parse chunk: "
+ << parser.getLastError()
+ << "."
+ << std::endl;
+ return false;
+ }
+
+ if (parser.getChunk()->type != android::RES_STRING_POOL_TYPE) {
+ Logger::error(mSource)
+ << "expected Symbol string pool."
+ << std::endl;
+ return false;
+ }
+
+ if (mSymbolPool.setTo(parser.getChunk(), parser.getChunk()->size) != NO_ERROR) {
+ Logger::error(mSource)
+ << "failed to parse symbol string pool with code: "
+ << mSymbolPool.getError()
+ << "."
+ << std::endl;
+ return false;
+ }
+ return true;
+}
+
+bool BinaryResourceParser::parseTable(const ResChunk_header* chunk) {
+ const ResTable_header* tableHeader = convertTo<ResTable_header>(chunk);
+ if (!tableHeader) {
+ Logger::error(mSource)
+ << "could not parse chunk as ResTable_header."
+ << std::endl;
+ return false;
+ }
+
+ ResChunkPullParser parser(getChunkData(tableHeader->header),
+ getChunkDataLen(tableHeader->header));
+ while (ResChunkPullParser::isGoodEvent(parser.next())) {
+ switch (parser.getChunk()->type) {
+ case android::RES_STRING_POOL_TYPE:
+ if (mValuePool.getError() == NO_INIT) {
+ if (mValuePool.setTo(parser.getChunk(), parser.getChunk()->size) !=
+ NO_ERROR) {
+ Logger::error(mSource)
+ << "failed to parse value string pool with code: "
+ << mValuePool.getError()
+ << "."
+ << std::endl;
+ return false;
+ }
+
+ // Reserve some space for the strings we are going to add.
+ mTable->getValueStringPool().hintWillAdd(
+ mValuePool.size(), mValuePool.styleCount());
+ } else {
+ Logger::warn(mSource)
+ << "unexpected string pool."
+ << std::endl;
+ }
+ break;
+
+ case RES_TABLE_SYMBOL_TABLE_TYPE:
+ if (!parseSymbolTable(parser.getChunk())) {
+ return false;
+ }
+ break;
+
+ case RES_TABLE_SOURCE_POOL_TYPE: {
+ if (mSourcePool.setTo(getChunkData(*parser.getChunk()),
+ getChunkDataLen(*parser.getChunk())) != NO_ERROR) {
+ Logger::error(mSource)
+ << "failed to parse source pool with code: "
+ << mSourcePool.getError()
+ << "."
+ << std::endl;
+ return false;
+ }
+ break;
+ }
+
+ case android::RES_TABLE_PACKAGE_TYPE:
+ if (!parsePackage(parser.getChunk())) {
+ return false;
+ }
+ break;
+
+ default:
+ Logger::warn(mSource)
+ << "unexpected chunk of type "
+ << parser.getChunk()->type
+ << "."
+ << std::endl;
+ break;
+ }
+ }
+
+ if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
+ Logger::error(mSource)
+ << "bad resource table: " << parser.getLastError()
+ << "."
+ << std::endl;
+ return false;
+ }
+ return true;
+}
+
+bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) {
+ if (mValuePool.getError() != NO_ERROR) {
+ Logger::error(mSource)
+ << "no value string pool for ResTable."
+ << std::endl;
+ return false;
+ }
+
+ const ResTable_package* packageHeader = convertTo<ResTable_package>(chunk);
+ if (!packageHeader) {
+ Logger::error(mSource)
+ << "could not parse chunk as ResTable_header."
+ << std::endl;
+ return false;
+ }
+
+ if (mTable->getPackageId() == ResourceTable::kUnsetPackageId) {
+ // This is the first time the table has it's package ID set.
+ mTable->setPackageId(packageHeader->id);
+ } else if (mTable->getPackageId() != packageHeader->id) {
+ Logger::error(mSource)
+ << "ResTable_package has package ID "
+ << std::hex << packageHeader->id << std::dec
+ << " but ResourceTable has package ID "
+ << std::hex << mTable->getPackageId() << std::dec
+ << std::endl;
+ return false;
+ }
+
+ size_t len = strnlen16(reinterpret_cast<const char16_t*>(packageHeader->name),
+ sizeof(packageHeader->name) / sizeof(packageHeader->name[0]));
+ mTable->setPackage(StringPiece16(reinterpret_cast<const char16_t*>(packageHeader->name), len));
+
+ ResChunkPullParser parser(getChunkData(packageHeader->header),
+ getChunkDataLen(packageHeader->header));
+ while (ResChunkPullParser::isGoodEvent(parser.next())) {
+ switch (parser.getChunk()->type) {
+ case android::RES_STRING_POOL_TYPE:
+ if (mTypePool.getError() == NO_INIT) {
+ if (mTypePool.setTo(parser.getChunk(), parser.getChunk()->size) !=
+ NO_ERROR) {
+ Logger::error(mSource)
+ << "failed to parse type string pool with code "
+ << mTypePool.getError()
+ << "."
+ << std::endl;
+ return false;
+ }
+ } else if (mKeyPool.getError() == NO_INIT) {
+ if (mKeyPool.setTo(parser.getChunk(), parser.getChunk()->size) !=
+ NO_ERROR) {
+ Logger::error(mSource)
+ << "failed to parse key string pool with code "
+ << mKeyPool.getError()
+ << "."
+ << std::endl;
+ return false;
+ }
+ } else {
+ Logger::warn(mSource)
+ << "unexpected string pool."
+ << std::endl;
+ }
+ break;
+
+ case android::RES_TABLE_TYPE_SPEC_TYPE:
+ if (!parseTypeSpec(parser.getChunk())) {
+ return false;
+ }
+ break;
+
+ case android::RES_TABLE_TYPE_TYPE:
+ if (!parseType(parser.getChunk())) {
+ return false;
+ }
+ break;
+
+ case RES_TABLE_PUBLIC_TYPE:
+ if (!parsePublic(parser.getChunk())) {
+ return false;
+ }
+ break;
+
+ default:
+ Logger::warn(mSource)
+ << "unexpected chunk of type "
+ << parser.getChunk()->type
+ << "."
+ << std::endl;
+ break;
+ }
+ }
+
+ if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
+ Logger::error(mSource)
+ << "bad package: "
+ << parser.getLastError()
+ << "."
+ << std::endl;
+ return false;
+ }
+
+ // Now go through the table and change resource ID references to
+ // symbolic references.
+
+ ReferenceIdToNameVisitor visitor(mResolver, &mIdIndex);
+ for (auto& type : *mTable) {
+ for (auto& entry : type->entries) {
+ for (auto& configValue : entry->values) {
+ configValue.value->accept(visitor, {});
+ }
+ }
+ }
+ return true;
+}
+
+bool BinaryResourceParser::parsePublic(const ResChunk_header* chunk) {
+ const Public_header* header = convertTo<Public_header>(chunk);
+
+ if (header->typeId == 0) {
+ Logger::error(mSource)
+ << "invalid type ID " << header->typeId << std::endl;
+ return false;
+ }
+
+ const ResourceType* parsedType = parseResourceType(util::getString(mTypePool,
+ header->typeId - 1));
+ if (!parsedType) {
+ Logger::error(mSource)
+ << "invalid type " << util::getString(mTypePool, header->typeId - 1) << std::endl;
+ return false;
+ }
+
+ const uintptr_t chunkEnd = reinterpret_cast<uintptr_t>(chunk) + chunk->size;
+ const Public_entry* entry = reinterpret_cast<const Public_entry*>(
+ getChunkData(header->header));
+ for (uint32_t i = 0; i < header->count; i++) {
+ if (reinterpret_cast<uintptr_t>(entry) + sizeof(*entry) > chunkEnd) {
+ Logger::error(mSource)
+ << "Public_entry extends beyond chunk."
+ << std::endl;
+ return false;
+ }
+
+ const ResourceId resId = { mTable->getPackageId(), header->typeId, entry->entryId };
+ const ResourceName name = {
+ mTable->getPackage(),
+ *parsedType,
+ util::getString(mKeyPool, entry->key.index).toString() };
+
+ SourceLine source;
+ if (mSourcePool.getError() == NO_ERROR) {
+ source.path = util::utf16ToUtf8(util::getString(mSourcePool, entry->source.index));
+ source.line = entry->sourceLine;
+ }
+
+ if (!mTable->markPublicAllowMangled(name, resId, source)) {
+ return false;
+ }
+
+ // Add this resource name->id mapping to the index so
+ // that we can resolve all ID references to name references.
+ auto cacheIter = mIdIndex.find(resId);
+ if (cacheIter == mIdIndex.end()) {
+ mIdIndex.insert({ resId, name });
+ }
+
+ entry++;
+ }
+ return true;
+}
+
+bool BinaryResourceParser::parseTypeSpec(const ResChunk_header* chunk) {
+ if (mTypePool.getError() != NO_ERROR) {
+ Logger::error(mSource)
+ << "no type string pool available for ResTable_typeSpec."
+ << std::endl;
+ return false;
+ }
+
+ const ResTable_typeSpec* typeSpec = convertTo<ResTable_typeSpec>(chunk);
+ if (!typeSpec) {
+ Logger::error(mSource)
+ << "could not parse chunk as ResTable_typeSpec."
+ << std::endl;
+ return false;
+ }
+
+ if (typeSpec->id == 0) {
+ Logger::error(mSource)
+ << "ResTable_typeSpec has invalid id: "
+ << typeSpec->id
+ << "."
+ << std::endl;
+ return false;
+ }
+ return true;
+}
+
+bool BinaryResourceParser::parseType(const ResChunk_header* chunk) {
+ if (mTypePool.getError() != NO_ERROR) {
+ Logger::error(mSource)
+ << "no type string pool available for ResTable_typeSpec."
+ << std::endl;
+ return false;
+ }
+
+ if (mKeyPool.getError() != NO_ERROR) {
+ Logger::error(mSource)
+ << "no key string pool available for ResTable_type."
+ << std::endl;
+ return false;
+ }
+
+ const ResTable_type* type = convertTo<ResTable_type>(chunk);
+ if (!type) {
+ Logger::error(mSource)
+ << "could not parse chunk as ResTable_type."
+ << std::endl;
+ return false;
+ }
+
+ if (type->id == 0) {
+ Logger::error(mSource)
+ << "ResTable_type has invalid id: "
+ << type->id
+ << "."
+ << std::endl;
+ return false;
+ }
+
+ const ConfigDescription config(type->config);
+ const StringPiece16 typeName = util::getString(mTypePool, type->id - 1);
+
+ const ResourceType* parsedType = parseResourceType(typeName);
+ if (!parsedType) {
+ Logger::error(mSource)
+ << "invalid type name '"
+ << typeName
+ << "' for type with ID "
+ << uint32_t(type->id)
+ << "." << std::endl;
+ return false;
+ }
+
+ android::TypeVariant tv(type);
+ for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) {
+ if (!*it) {
+ continue;
+ }
+
+ const ResTable_entry* entry = *it;
+ const ResourceName name = {
+ mTable->getPackage(),
+ *parsedType,
+ util::getString(mKeyPool, entry->key.index).toString()
+ };
+
+ const ResourceId resId = { mTable->getPackageId(), type->id, it.index() };
+
+ std::unique_ptr<Value> resourceValue;
+ const ResTable_entry_source* sourceBlock = nullptr;
+ if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
+ const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
+ if (mapEntry->size - sizeof(*mapEntry) == sizeof(*sourceBlock)) {
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(mapEntry);
+ data += mapEntry->size - sizeof(*sourceBlock);
+ sourceBlock = reinterpret_cast<const ResTable_entry_source*>(data);
+ }
+
+ // TODO(adamlesinski): Check that the entry count is valid.
+ resourceValue = parseMapEntry(name, config, mapEntry);
+ } else {
+ if (entry->size - sizeof(*entry) == sizeof(*sourceBlock)) {
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(entry);
+ data += entry->size - sizeof(*sourceBlock);
+ sourceBlock = reinterpret_cast<const ResTable_entry_source*>(data);
+ }
+
+ const Res_value* value = reinterpret_cast<const Res_value*>(
+ reinterpret_cast<const uint8_t*>(entry) + entry->size);
+ resourceValue = parseValue(name, config, value, entry->flags);
+ }
+
+ if (!resourceValue) {
+ // TODO(adamlesinski): For now this is ok, but it really shouldn't be.
+ continue;
+ }
+
+ SourceLine source = mSource.line(0);
+ if (sourceBlock) {
+ size_t len;
+ const char* str = mSourcePool.string8At(sourceBlock->pathIndex, &len);
+ if (str) {
+ source.path.assign(str, len);
+ }
+ source.line = sourceBlock->line;
+ }
+
+ if (!mTable->addResourceAllowMangled(name, config, source, std::move(resourceValue))) {
+ return false;
+ }
+
+ if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
+ if (!mTable->markPublicAllowMangled(name, resId, mSource.line(0))) {
+ return false;
+ }
+ }
+
+ // Add this resource name->id mapping to the index so
+ // that we can resolve all ID references to name references.
+ auto cacheIter = mIdIndex.find(resId);
+ if (cacheIter == mIdIndex.end()) {
+ mIdIndex.insert({ resId, name });
+ }
+ }
+ return true;
+}
+
+std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const Res_value* value,
+ uint16_t flags) {
+ if (name.type == ResourceType::kId) {
+ return util::make_unique<Id>();
+ }
+
+ if (value->dataType == Res_value::TYPE_STRING) {
+ StringPiece16 str = util::getString(mValuePool, value->data);
+
+ const ResStringPool_span* spans = mValuePool.styleAt(value->data);
+ if (spans != nullptr) {
+ StyleString styleStr = { str.toString() };
+ while (spans->name.index != ResStringPool_span::END) {
+ styleStr.spans.push_back(Span{
+ util::getString(mValuePool, spans->name.index).toString(),
+ spans->firstChar,
+ spans->lastChar
+ });
+ spans++;
+ }
+ return util::make_unique<StyledString>(
+ mTable->getValueStringPool().makeRef(
+ styleStr, StringPool::Context{1, config}));
+ } else {
+ if (name.type != ResourceType::kString &&
+ util::stringStartsWith<char16_t>(str, u"res/")) {
+ // This must be a FileReference.
+ return util::make_unique<FileReference>(mTable->getValueStringPool().makeRef(
+ str, StringPool::Context{ 0, config }));
+ }
+
+ // There are no styles associated with this string, so treat it as
+ // a simple string.
+ return util::make_unique<String>(
+ mTable->getValueStringPool().makeRef(
+ str, StringPool::Context{1, config}));
+ }
+ }
+
+ if (value->dataType == Res_value::TYPE_REFERENCE ||
+ value->dataType == Res_value::TYPE_ATTRIBUTE) {
+ const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE) ?
+ Reference::Type::kResource : Reference::Type::kAttribute;
+
+ if (value->data != 0) {
+ // This is a normal reference.
+ return util::make_unique<Reference>(value->data, type);
+ }
+
+ // This reference has an invalid ID. Check if it is an unresolved symbol.
+ ResourceNameRef symbol;
+ if (getSymbol(&value->data, &symbol)) {
+ return util::make_unique<Reference>(symbol, type);
+ }
+
+ // This is not an unresolved symbol, so it must be the magic @null reference.
+ Res_value nullType = {};
+ nullType.dataType = Res_value::TYPE_REFERENCE;
+ return util::make_unique<BinaryPrimitive>(nullType);
+ }
+
+ if (value->dataType == ExtendedTypes::TYPE_RAW_STRING) {
+ return util::make_unique<RawString>(
+ mTable->getValueStringPool().makeRef(util::getString(mValuePool, value->data),
+ StringPool::Context{ 1, config }));
+ }
+
+ // Treat this as a raw binary primitive.
+ return util::make_unique<BinaryPrimitive>(*value);
+}
+
+std::unique_ptr<Value> BinaryResourceParser::parseMapEntry(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const ResTable_map_entry* map) {
+ switch (name.type) {
+ case ResourceType::kStyle:
+ return parseStyle(name, config, map);
+ case ResourceType::kAttr:
+ return parseAttr(name, config, map);
+ case ResourceType::kArray:
+ return parseArray(name, config, map);
+ case ResourceType::kStyleable:
+ return parseStyleable(name, config, map);
+ case ResourceType::kPlurals:
+ return parsePlural(name, config, map);
+ default:
+ break;
+ }
+ return {};
+}
+
+std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const ResTable_map_entry* map) {
+ std::unique_ptr<Style> style = util::make_unique<Style>();
+ if (map->parent.ident == 0) {
+ // The parent is either not set or it is an unresolved symbol.
+ // Check to see if it is a symbol.
+ ResourceNameRef symbol;
+ if (getSymbol(&map->parent.ident, &symbol)) {
+ style->parent.name = symbol.toResourceName();
+ }
+ } else {
+ // The parent is a regular reference to a resource.
+ style->parent.id = map->parent.ident;
+ }
+
+ for (const ResTable_map& mapEntry : map) {
+ style->entries.emplace_back();
+ Style::Entry& styleEntry = style->entries.back();
+
+ if (mapEntry.name.ident == 0) {
+ // The map entry's key (attribute) is not set. This must be
+ // a symbol reference, so resolve it.
+ ResourceNameRef symbol;
+ bool result = getSymbol(&mapEntry.name.ident, &symbol);
+ assert(result);
+ styleEntry.key.name = symbol.toResourceName();
+ } else {
+ // The map entry's key (attribute) is a regular reference.
+ styleEntry.key.id = mapEntry.name.ident;
+ }
+
+ // Parse the attribute's value.
+ styleEntry.value = parseValue(name, config, &mapEntry.value, 0);
+ assert(styleEntry.value);
+ }
+ return style;
+}
+
+std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const ResTable_map_entry* map) {
+ const bool isWeak = (map->flags & ResTable_entry::FLAG_WEAK) != 0;
+ std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak);
+
+ // First we must discover what type of attribute this is. Find the type mask.
+ auto typeMaskIter = std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool {
+ return entry.name.ident == ResTable_map::ATTR_TYPE;
+ });
+
+ if (typeMaskIter != end(map)) {
+ attr->typeMask = typeMaskIter->value.data;
+ }
+
+ if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
+ for (const ResTable_map& mapEntry : map) {
+ if (Res_INTERNALID(mapEntry.name.ident)) {
+ continue;
+ }
+
+ Attribute::Symbol symbol;
+ symbol.value = mapEntry.value.data;
+ if (mapEntry.name.ident == 0) {
+ // The map entry's key (id) is not set. This must be
+ // a symbol reference, so resolve it.
+ ResourceNameRef symbolName;
+ bool result = getSymbol(&mapEntry.name.ident, &symbolName);
+ assert(result);
+ symbol.symbol.name = symbolName.toResourceName();
+ } else {
+ // The map entry's key (id) is a regular reference.
+ symbol.symbol.id = mapEntry.name.ident;
+ }
+
+ attr->symbols.push_back(std::move(symbol));
+ }
+ }
+
+ // TODO(adamlesinski): Find min, max, i80n, etc attributes.
+ return attr;
+}
+
+std::unique_ptr<Array> BinaryResourceParser::parseArray(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const ResTable_map_entry* map) {
+ std::unique_ptr<Array> array = util::make_unique<Array>();
+ for (const ResTable_map& mapEntry : map) {
+ array->items.push_back(parseValue(name, config, &mapEntry.value, 0));
+ }
+ return array;
+}
+
+std::unique_ptr<Styleable> BinaryResourceParser::parseStyleable(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const ResTable_map_entry* map) {
+ std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
+ for (const ResTable_map& mapEntry : map) {
+ if (mapEntry.name.ident == 0) {
+ // The map entry's key (attribute) is not set. This must be
+ // a symbol reference, so resolve it.
+ ResourceNameRef symbol;
+ bool result = getSymbol(&mapEntry.name.ident, &symbol);
+ assert(result);
+ styleable->entries.emplace_back(symbol);
+ } else {
+ // The map entry's key (attribute) is a regular reference.
+ styleable->entries.emplace_back(mapEntry.name.ident);
+ }
+ }
+ return styleable;
+}
+
+std::unique_ptr<Plural> BinaryResourceParser::parsePlural(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const ResTable_map_entry* map) {
+ std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+ for (const ResTable_map& mapEntry : map) {
+ std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0);
+
+ switch (mapEntry.name.ident) {
+ case android::ResTable_map::ATTR_ZERO:
+ plural->values[Plural::Zero] = std::move(item);
+ break;
+ case android::ResTable_map::ATTR_ONE:
+ plural->values[Plural::One] = std::move(item);
+ break;
+ case android::ResTable_map::ATTR_TWO:
+ plural->values[Plural::Two] = std::move(item);
+ break;
+ case android::ResTable_map::ATTR_FEW:
+ plural->values[Plural::Few] = std::move(item);
+ break;
+ case android::ResTable_map::ATTR_MANY:
+ plural->values[Plural::Many] = std::move(item);
+ break;
+ case android::ResTable_map::ATTR_OTHER:
+ plural->values[Plural::Other] = std::move(item);
+ break;
+ }
+ }
+ return plural;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/BinaryResourceParser.h b/tools/aapt2/BinaryResourceParser.h
new file mode 100644
index 0000000..32876cd
--- /dev/null
+++ b/tools/aapt2/BinaryResourceParser.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_BINARY_RESOURCE_PARSER_H
+#define AAPT_BINARY_RESOURCE_PARSER_H
+
+#include "Resolver.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "Source.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <string>
+
+namespace aapt {
+
+struct SymbolTable_entry;
+
+/*
+ * Parses a binary resource table (resources.arsc) and adds the entries
+ * to a ResourceTable. This is different than the libandroidfw ResTable
+ * in that it scans the table from top to bottom and doesn't require
+ * support for random access. It is also able to parse non-runtime
+ * 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(const std::shared_ptr<ResourceTable>& table,
+ const std::shared_ptr<IResolver>& resolver,
+ const Source& source,
+ const void* data, size_t len);
+
+ BinaryResourceParser(const BinaryResourceParser&) = delete; // No copy.
+
+ /*
+ * Parses the binary resource table and returns true if successful.
+ */
+ bool parse();
+
+private:
+ // Helper method to retrieve the symbol name for a given table offset specified
+ // as a pointer.
+ bool getSymbol(const void* data, ResourceNameRef* outSymbol);
+
+ bool parseTable(const android::ResChunk_header* chunk);
+ bool parseSymbolTable(const android::ResChunk_header* chunk);
+
+ // Looks up the resource ID in the reference and converts it to a name if available.
+ bool idToName(Reference* reference);
+
+ bool parsePackage(const android::ResChunk_header* chunk);
+ bool parsePublic(const android::ResChunk_header* chunk);
+ bool parseTypeSpec(const android::ResChunk_header* chunk);
+ bool parseType(const android::ResChunk_header* chunk);
+
+ 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<Styleable> parseStyleable(const ResourceNameRef& name,
+ const ConfigDescription& config, const android::ResTable_map_entry* map);
+
+ std::shared_ptr<ResourceTable> mTable;
+
+ std::shared_ptr<IResolver> mResolver;
+
+ const Source mSource;
+
+ const void* mData;
+ const size_t mDataLen;
+
+ // The package name of the resource table.
+ std::u16string mPackage;
+
+ // The array of symbol entries. Each element points to an offset
+ // in the table and an index into the symbol table string pool.
+ const SymbolTable_entry* mSymbolEntries = nullptr;
+
+ // Number of symbol entries.
+ size_t mSymbolEntryCount = 0;
+
+ // The symbol table string pool. Holds the names of symbols
+ // referenced in this table but not defined nor resolved to an
+ // ID.
+ android::ResStringPool mSymbolPool;
+
+ // The source string pool. Resource entries may have an extra
+ // field that points into this string pool, which denotes where
+ // the resource was parsed from originally.
+ android::ResStringPool mSourcePool;
+
+ // The standard value string pool for resource values.
+ android::ResStringPool mValuePool;
+
+ // 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 entries defined
+ // in this table.
+ android::ResStringPool mKeyPool;
+
+ // 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;
+};
+
+} // namespace aapt
+
+namespace android {
+
+/**
+ * Iterator functionality for ResTable_map_entry.
+ */
+
+inline const ResTable_map* begin(const ResTable_map_entry* map) {
+ return reinterpret_cast<const ResTable_map*>(
+ reinterpret_cast<const uint8_t*>(map) + map->size);
+}
+
+inline const ResTable_map* end(const ResTable_map_entry* map) {
+ return reinterpret_cast<const ResTable_map*>(
+ reinterpret_cast<const uint8_t*>(map) + map->size) + map->count;
+}
+
+} // namespace android
+
+#endif // AAPT_BINARY_RESOURCE_PARSER_H
diff --git a/tools/aapt2/BindingXmlPullParser.cpp b/tools/aapt2/BindingXmlPullParser.cpp
new file mode 100644
index 0000000..4b7a656
--- /dev/null
+++ b/tools/aapt2/BindingXmlPullParser.cpp
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BindingXmlPullParser.h"
+#include "Util.h"
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+constexpr const char16_t* kBindingNamespaceUri = u"http://schemas.android.com/apk/binding";
+constexpr const char16_t* kAndroidNamespaceUri = u"http://schemas.android.com/apk/res/android";
+constexpr const char16_t* kVariableTagName = u"variable";
+constexpr const char* kBindingTagPrefix = "android:binding_";
+
+BindingXmlPullParser::BindingXmlPullParser(const std::shared_ptr<XmlPullParser>& parser) :
+ mParser(parser), mOverride(false), mNextTagId(0) {
+}
+
+bool BindingXmlPullParser::readVariableDeclaration() {
+ VarDecl var;
+
+ const auto endAttrIter = mParser->endAttributes();
+ for (auto attrIter = mParser->beginAttributes(); attrIter != endAttrIter; ++attrIter) {
+ if (!attrIter->namespaceUri.empty()) {
+ continue;
+ }
+
+ if (attrIter->name == u"name") {
+ var.name = util::utf16ToUtf8(attrIter->value);
+ } else if (attrIter->name == u"type") {
+ var.type = util::utf16ToUtf8(attrIter->value);
+ }
+ }
+
+ XmlPullParser::skipCurrentElement(mParser.get());
+
+ if (var.name.empty()) {
+ mLastError = "variable declaration missing name";
+ return false;
+ }
+
+ if (var.type.empty()) {
+ mLastError = "variable declaration missing type";
+ return false;
+ }
+
+ mVarDecls.push_back(std::move(var));
+ return true;
+}
+
+bool BindingXmlPullParser::readExpressions() {
+ mOverride = true;
+ std::vector<XmlPullParser::Attribute> expressions;
+ std::string idValue;
+
+ const auto endAttrIter = mParser->endAttributes();
+ for (auto attr = mParser->beginAttributes(); attr != endAttrIter; ++attr) {
+ if (attr->namespaceUri == kAndroidNamespaceUri && attr->name == u"id") {
+ idValue = util::utf16ToUtf8(attr->value);
+ } else {
+ StringPiece16 value = util::trimWhitespace(attr->value);
+ if (util::stringStartsWith<char16_t>(value, u"@{") &&
+ util::stringEndsWith<char16_t>(value, u"}")) {
+ // This is attribute's value is an expression of the form
+ // @{expression}. We need to capture the expression inside.
+ expressions.push_back(XmlPullParser::Attribute{
+ attr->namespaceUri,
+ attr->name,
+ value.substr(2, value.size() - 3).toString()
+ });
+ } else {
+ // This is a normal attribute, use as is.
+ mAttributes.emplace_back(*attr);
+ }
+ }
+ }
+
+ // Check if we have any expressions.
+ if (!expressions.empty()) {
+ // We have expressions, so let's assign the target a tag number
+ // and add it to our targets list.
+ int32_t targetId = mNextTagId++;
+ mTargets.push_back(Target{
+ util::utf16ToUtf8(mParser->getElementName()),
+ idValue,
+ targetId,
+ std::move(expressions)
+ });
+
+ std::stringstream numGen;
+ numGen << kBindingTagPrefix << targetId;
+ mAttributes.push_back(XmlPullParser::Attribute{
+ std::u16string(kAndroidNamespaceUri),
+ std::u16string(u"tag"),
+ util::utf8ToUtf16(numGen.str())
+ });
+ }
+ return true;
+}
+
+XmlPullParser::Event BindingXmlPullParser::next() {
+ // Clear old state in preparation for the next event.
+ mOverride = false;
+ mAttributes.clear();
+
+ while (true) {
+ Event event = mParser->next();
+ if (event == Event::kStartElement) {
+ if (mParser->getElementNamespace().empty() &&
+ mParser->getElementName() == kVariableTagName) {
+ // This is a variable tag. Record data from it, and
+ // then discard the entire element.
+ if (!readVariableDeclaration()) {
+ // mLastError is set, so getEvent will return kBadDocument.
+ return getEvent();
+ }
+ continue;
+ } else {
+ // Check for expressions of the form @{} in attribute text.
+ const auto endAttrIter = mParser->endAttributes();
+ for (auto attr = mParser->beginAttributes(); attr != endAttrIter; ++attr) {
+ StringPiece16 value = util::trimWhitespace(attr->value);
+ if (util::stringStartsWith<char16_t>(value, u"@{") &&
+ util::stringEndsWith<char16_t>(value, u"}")) {
+ if (!readExpressions()) {
+ return getEvent();
+ }
+ break;
+ }
+ }
+ }
+ } else if (event == Event::kStartNamespace || event == Event::kEndNamespace) {
+ if (mParser->getNamespaceUri() == kBindingNamespaceUri) {
+ // Skip binding namespace tags.
+ continue;
+ }
+ }
+ return event;
+ }
+ return Event::kBadDocument;
+}
+
+bool BindingXmlPullParser::writeToFile(std::ostream& out) const {
+ out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+ out << "<Layout directory=\"\" layout=\"\" layoutId=\"\">\n";
+
+ // Write the variables.
+ out << " <Variables>\n";
+ for (const VarDecl& v : mVarDecls) {
+ out << " <entries name=\"" << v.name << "\" type=\"" << v.type << "\"/>\n";
+ }
+ out << " </Variables>\n";
+
+ // Write the imports.
+
+ std::stringstream tagGen;
+
+ // Write the targets.
+ out << " <Targets>\n";
+ for (const Target& t : mTargets) {
+ tagGen.str({});
+ tagGen << kBindingTagPrefix << t.tagId;
+ out << " <Target boundClass=\"" << t.className << "\" id=\"" << t.id
+ << "\" tag=\"" << tagGen.str() << "\">\n";
+ out << " <Expressions>\n";
+ for (const XmlPullParser::Attribute& a : t.expressions) {
+ out << " <Expression attribute=\"" << a.namespaceUri << ":" << a.name
+ << "\" text=\"" << a.value << "\"/>\n";
+ }
+ out << " </Expressions>\n";
+ out << " </Target>\n";
+ }
+ out << " </Targets>\n";
+
+ out << "</Layout>\n";
+ return bool(out);
+}
+
+XmlPullParser::const_iterator BindingXmlPullParser::beginAttributes() const {
+ if (mOverride) {
+ return mAttributes.begin();
+ }
+ return mParser->beginAttributes();
+}
+
+XmlPullParser::const_iterator BindingXmlPullParser::endAttributes() const {
+ if (mOverride) {
+ return mAttributes.end();
+ }
+ return mParser->endAttributes();
+}
+
+size_t BindingXmlPullParser::getAttributeCount() const {
+ if (mOverride) {
+ return mAttributes.size();
+ }
+ return mParser->getAttributeCount();
+}
+
+XmlPullParser::Event BindingXmlPullParser::getEvent() const {
+ if (!mLastError.empty()) {
+ return Event::kBadDocument;
+ }
+ return mParser->getEvent();
+}
+
+const std::string& BindingXmlPullParser::getLastError() const {
+ if (!mLastError.empty()) {
+ return mLastError;
+ }
+ return mParser->getLastError();
+}
+
+const std::u16string& BindingXmlPullParser::getComment() const {
+ return mParser->getComment();
+}
+
+size_t BindingXmlPullParser::getLineNumber() const {
+ return mParser->getLineNumber();
+}
+
+size_t BindingXmlPullParser::getDepth() const {
+ return mParser->getDepth();
+}
+
+const std::u16string& BindingXmlPullParser::getText() const {
+ return mParser->getText();
+}
+
+const std::u16string& BindingXmlPullParser::getNamespacePrefix() const {
+ return mParser->getNamespacePrefix();
+}
+
+const std::u16string& BindingXmlPullParser::getNamespaceUri() const {
+ return mParser->getNamespaceUri();
+}
+
+bool BindingXmlPullParser::applyPackageAlias(std::u16string* package,
+ const std::u16string& defaultPackage) const {
+ return mParser->applyPackageAlias(package, defaultPackage);
+}
+
+const std::u16string& BindingXmlPullParser::getElementNamespace() const {
+ return mParser->getElementNamespace();
+}
+
+const std::u16string& BindingXmlPullParser::getElementName() const {
+ return mParser->getElementName();
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/BindingXmlPullParser.h b/tools/aapt2/BindingXmlPullParser.h
new file mode 100644
index 0000000..cfb16ef
--- /dev/null
+++ b/tools/aapt2/BindingXmlPullParser.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_BINDING_XML_PULL_PARSER_H
+#define AAPT_BINDING_XML_PULL_PARSER_H
+
+#include "XmlPullParser.h"
+
+#include <iostream>
+#include <memory>
+#include <string>
+
+namespace aapt {
+
+class BindingXmlPullParser : public XmlPullParser {
+public:
+ BindingXmlPullParser(const std::shared_ptr<XmlPullParser>& parser);
+ BindingXmlPullParser(const BindingXmlPullParser& rhs) = delete;
+
+ Event getEvent() const override;
+ const std::string& getLastError() const override;
+ Event next() override;
+
+ const std::u16string& getComment() const override;
+ size_t getLineNumber() const override;
+ size_t getDepth() const override;
+
+ const std::u16string& getText() const override;
+
+ const std::u16string& getNamespacePrefix() const override;
+ const std::u16string& getNamespaceUri() const override;
+ bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage)
+ const override;
+
+ const std::u16string& getElementNamespace() const override;
+ const std::u16string& getElementName() const override;
+
+ const_iterator beginAttributes() const override;
+ const_iterator endAttributes() const override;
+ size_t getAttributeCount() const override;
+
+ bool writeToFile(std::ostream& out) const;
+
+private:
+ struct VarDecl {
+ std::string name;
+ std::string type;
+ };
+
+ struct Import {
+ std::string name;
+ std::string type;
+ };
+
+ struct Target {
+ std::string className;
+ std::string id;
+ int32_t tagId;
+
+ std::vector<XmlPullParser::Attribute> expressions;
+ };
+
+ bool readVariableDeclaration();
+ bool readExpressions();
+
+ std::shared_ptr<XmlPullParser> mParser;
+ std::string mLastError;
+ bool mOverride;
+ std::vector<XmlPullParser::Attribute> mAttributes;
+ std::vector<VarDecl> mVarDecls;
+ std::vector<Target> mTargets;
+ int32_t mNextTagId;
+};
+
+} // namespace aapt
+
+#endif // AAPT_BINDING_XML_PULL_PARSER_H
diff --git a/tools/aapt2/BindingXmlPullParser_test.cpp b/tools/aapt2/BindingXmlPullParser_test.cpp
new file mode 100644
index 0000000..28edcb6
--- /dev/null
+++ b/tools/aapt2/BindingXmlPullParser_test.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SourceXmlPullParser.h"
+#include "BindingXmlPullParser.h"
+
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+constexpr const char16_t* kAndroidNamespaceUri = u"http://schemas.android.com/apk/res/android";
+
+TEST(BindingXmlPullParserTest, SubstituteBindingExpressionsWithTag) {
+ std::stringstream input;
+ input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ << " xmlns:bind=\"http://schemas.android.com/apk/binding\"\n"
+ << " android:id=\"@+id/content\">\n"
+ << " <variable name=\"user\" type=\"com.android.test.User\"/>\n"
+ << " <TextView android:text=\"@{user.name}\" android:layout_width=\"wrap_content\"\n"
+ << " android:layout_height=\"wrap_content\"/>\n"
+ << "</LinearLayout>\n";
+ std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
+ BindingXmlPullParser parser(sourceParser);
+
+ ASSERT_EQ(XmlPullParser::Event::kStartNamespace, parser.next());
+ EXPECT_EQ(std::u16string(u"http://schemas.android.com/apk/res/android"),
+ parser.getNamespaceUri());
+
+ ASSERT_EQ(XmlPullParser::Event::kStartElement, parser.next());
+ EXPECT_EQ(std::u16string(u"LinearLayout"), parser.getElementName());
+
+ while (parser.next() == XmlPullParser::Event::kText) {}
+
+ ASSERT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+ EXPECT_EQ(std::u16string(u"TextView"), parser.getElementName());
+
+ ASSERT_EQ(3u, parser.getAttributeCount());
+ const auto endAttr = parser.endAttributes();
+ EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"layout_width"));
+ EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"layout_height"));
+ EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"tag"));
+
+ while (parser.next() == XmlPullParser::Event::kText) {}
+
+ ASSERT_EQ(XmlPullParser::Event::kEndElement, parser.getEvent());
+
+ while (parser.next() == XmlPullParser::Event::kText) {}
+
+ ASSERT_EQ(XmlPullParser::Event::kEndElement, parser.getEvent());
+ ASSERT_EQ(XmlPullParser::Event::kEndNamespace, parser.next());
+}
+
+TEST(BindingXmlPullParserTest, GenerateVariableDeclarations) {
+ std::stringstream input;
+ input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ << " xmlns:bind=\"http://schemas.android.com/apk/binding\"\n"
+ << " android:id=\"@+id/content\">\n"
+ << " <variable name=\"user\" type=\"com.android.test.User\"/>\n"
+ << "</LinearLayout>\n";
+ std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
+ BindingXmlPullParser parser(sourceParser);
+
+ while (XmlPullParser::isGoodEvent(parser.next())) {
+ ASSERT_NE(XmlPullParser::Event::kBadDocument, parser.getEvent());
+ }
+
+ std::stringstream output;
+ ASSERT_TRUE(parser.writeToFile(output));
+
+ std::string result = output.str();
+ EXPECT_NE(std::string::npos,
+ result.find("<entries name=\"user\" type=\"com.android.test.User\"/>"));
+}
+
+TEST(BindingXmlPullParserTest, FailOnMissingNameOrTypeInVariableDeclaration) {
+ std::stringstream input;
+ input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ << " xmlns:bind=\"http://schemas.android.com/apk/binding\"\n"
+ << " android:id=\"@+id/content\">\n"
+ << " <variable name=\"user\"/>\n"
+ << "</LinearLayout>\n";
+ std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
+ BindingXmlPullParser parser(sourceParser);
+
+ while (XmlPullParser::isGoodEvent(parser.next())) {}
+
+ EXPECT_EQ(XmlPullParser::Event::kBadDocument, parser.getEvent());
+ EXPECT_FALSE(parser.getLastError().empty());
+}
+
+
+} // namespace aapt
diff --git a/tools/aapt2/Compat_test.cpp b/tools/aapt2/Compat_test.cpp
new file mode 100644
index 0000000..96aee44
--- /dev/null
+++ b/tools/aapt2/Compat_test.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(CompatTest, VersionAttributesInStyle) {
+}
+
+TEST(CompatTest, VersionAttributesInXML) {
+}
+
+TEST(CompatTest, DoNotOverrideExistingVersionedFiles) {
+}
+
+TEST(CompatTest, VersionAttributesInStyleWithCorrectPrecedence) {
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp
new file mode 100644
index 0000000..6ddf94a
--- /dev/null
+++ b/tools/aapt2/ConfigDescription.cpp
@@ -0,0 +1,752 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ConfigDescription.h"
+#include "Locale.h"
+#include "SdkConstants.h"
+#include "StringPiece.h"
+#include "Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+using android::ResTable_config;
+
+static const char* kWildcardName = "any";
+
+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;
+ c++;
+
+ 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;
+ }
+
+ 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;
+ 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;
+
+ if (out) {
+ out->mnc = atoi(val);
+ if (out->mnc == 0) {
+ out->mnc = ACONFIGURATION_MNC_ZERO;
+ }
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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, "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, "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, "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, "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, "xxxhdpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_XXXHIGH;
+ return true;
+ }
+
+ 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;
+ }
+
+ // temporarily replace the first letter with \0 to
+ // use atoi.
+ char tmp = c[0];
+ c[0] = '\0';
+
+ int d = atoi(name);
+ c[0] = tmp;
+
+ if (d != 0) {
+ if (out) out->density = d;
+ return true;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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 (out) {
+ out->screenWidth = w;
+ out->screenHeight = h;
+ }
+
+ return true;
+}
+
+static bool parseSmallestScreenWidthDp(const char* name, ResTable_config* out) {
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) {
+ 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 (*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());
+ }
+
+ 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 (*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());
+ }
+
+ 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 (*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);
+
+ if (out) {
+ out->sdkVersion = (uint16_t)atoi(sdkName.c_str());
+ out->minorVersion = 0;
+ }
+
+ return true;
+}
+
+bool ConfigDescription::parse(const StringPiece& str, ConfigDescription* out) {
+ std::vector<std::string> parts = util::splitAndLowercase(str, '-');
+
+ ConfigDescription config;
+ ssize_t partsConsumed = 0;
+ LocaleValue locale;
+
+ const auto partsEnd = parts.end();
+ auto partIter = parts.begin();
+
+ if (str.size() == 0) {
+ goto success;
+ }
+
+ if (parseMcc(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseMnc(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ 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;
+ }
+ }
+
+ if (parseLayoutDirection(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseSmallestScreenWidthDp(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseScreenWidthDp(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseScreenHeightDp(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseScreenLayoutSize(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseScreenLayoutLong(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseOrientation(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseUiModeType(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseUiModeNight(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseDensity(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseTouchscreen(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseKeysHidden(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseKeyboard(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseNavHidden(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseNavigation(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseScreenSize(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseVersion(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ // Unrecognized.
+ return false;
+
+success:
+ if (out != NULL) {
+ applyVersionForCompatibility(&config);
+ *out = config;
+ }
+ return true;
+}
+
+void ConfigDescription::applyVersionForCompatibility(ConfigDescription* config) {
+ uint16_t minSdk = 0;
+ 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;
+ }
+
+ if (minSdk > config->sdkVersion) {
+ config->sdkVersion = minSdk;
+ }
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ConfigDescription.h b/tools/aapt2/ConfigDescription.h
new file mode 100644
index 0000000..67b4b75
--- /dev/null
+++ b/tools/aapt2/ConfigDescription.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_CONFIG_DESCRIPTION_H
+#define AAPT_CONFIG_DESCRIPTION_H
+
+#include "StringPiece.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <ostream>
+
+namespace aapt {
+
+/*
+ * Subclass of ResTable_config that adds convenient
+ * initialization and comparison methods.
+ */
+struct ConfigDescription : public android::ResTable_config {
+ /*
+ * 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);
+ 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;
+};
+
+inline ConfigDescription::ConfigDescription() {
+ 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);
+}
+
+inline ConfigDescription::ConfigDescription(const ConfigDescription& o) {
+ *static_cast<android::ResTable_config*>(this) = o;
+}
+
+inline ConfigDescription::ConfigDescription(ConfigDescription&& 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 ConfigDescription& o) {
+ *static_cast<android::ResTable_config*>(this) = o;
+ return *this;
+}
+
+inline ConfigDescription& ConfigDescription::operator=(ConfigDescription&& o) {
+ *this = o;
+ return *this;
+}
+
+inline bool ConfigDescription::operator<(const ConfigDescription& o) const {
+ return compare(o) < 0;
+}
+
+inline bool ConfigDescription::operator<=(const ConfigDescription& o) const {
+ return compare(o) <= 0;
+}
+
+inline bool ConfigDescription::operator==(const ConfigDescription& o) const {
+ return compare(o) == 0;
+}
+
+inline bool ConfigDescription::operator!=(const ConfigDescription& o) const {
+ return compare(o) != 0;
+}
+
+inline bool ConfigDescription::operator>=(const ConfigDescription& o) const {
+ return compare(o) >= 0;
+}
+
+inline bool ConfigDescription::operator>(const ConfigDescription& o) const {
+ return compare(o) > 0;
+}
+
+inline ::std::ostream& operator<<(::std::ostream& out, const ConfigDescription& o) {
+ return out << o.toString().string();
+}
+
+} // namespace aapt
+
+#endif // AAPT_CONFIG_DESCRIPTION_H
diff --git a/tools/aapt2/ConfigDescription_test.cpp b/tools/aapt2/ConfigDescription_test.cpp
new file mode 100644
index 0000000..c57e351
--- /dev/null
+++ b/tools/aapt2/ConfigDescription_test.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ConfigDescription.h"
+#include "StringPiece.h"
+
+#include <gtest/gtest.h>
+#include <string>
+
+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";
+}
+
+TEST(ConfigDescriptionTest, ParseFailWhenQualifiersAreOutOfOrder) {
+ 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"));
+}
+
+TEST(ConfigDescriptionTest, ParseFailWhenQualifiersHaveTrailingDash) {
+ 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());
+}
+
+TEST(ConfigDescriptionTest, ParseLocales) {
+ 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());
+
+ 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);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
new file mode 100644
index 0000000..cf222c6
--- /dev/null
+++ b/tools/aapt2/Debug.cpp
@@ -0,0 +1,192 @@
+/*
+ * 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 "Debug.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "Util.h"
+
+#include <algorithm>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <queue>
+#include <set>
+#include <vector>
+
+namespace aapt {
+
+struct PrintVisitor : ConstValueVisitor {
+ void visit(const Attribute& attr, ValueVisitorArgs&) 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.entry << " (" << symbol.symbol.id << ") = "
+ << symbol.value;
+ }
+ }
+ }
+
+ void visit(const Style& style, ValueVisitorArgs&) override {
+ std::cout << "(style)";
+ if (style.parent.name.isValid() || style.parent.id.isValid()) {
+ std::cout << " parent=";
+ if (style.parent.name.isValid()) {
+ std::cout << style.parent.name << " ";
+ }
+
+ if (style.parent.id.isValid()) {
+ std::cout << style.parent.id;
+ }
+ }
+
+ for (const auto& entry : style.entries) {
+ std::cout << "\n ";
+ if (entry.key.name.isValid()) {
+ std::cout << entry.key.name.package << ":" << entry.key.name.entry;
+ }
+
+ if (entry.key.id.isValid()) {
+ std::cout << "(" << entry.key.id << ")";
+ }
+
+ std::cout << "=" << *entry.value;
+ }
+ }
+
+ void visit(const Array& array, ValueVisitorArgs&) override {
+ array.print(std::cout);
+ }
+
+ void visit(const Plural& plural, ValueVisitorArgs&) override {
+ plural.print(std::cout);
+ }
+
+ void visit(const Styleable& styleable, ValueVisitorArgs&) override {
+ styleable.print(std::cout);
+ }
+
+ void visitItem(const Item& item, ValueVisitorArgs& args) override {
+ item.print(std::cout);
+ }
+};
+
+void Debug::printTable(const std::shared_ptr<ResourceTable>& table) {
+ std::cout << "Package name=" << table->getPackage();
+ if (table->getPackageId() != ResourceTable::kUnsetPackageId) {
+ std::cout << " id=" << std::hex << table->getPackageId() << std::dec;
+ }
+ std::cout << std::endl;
+
+ for (const auto& type : *table) {
+ std::cout << " type " << type->type;
+ if (type->typeId != ResourceTableType::kUnsetTypeId) {
+ std::cout << " id=" << std::hex << type->typeId << std::dec;
+ }
+ std::cout << " entryCount=" << type->entries.size() << std::endl;
+
+ std::vector<const ResourceEntry*> sortedEntries;
+ for (const auto& entry : type->entries) {
+ auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(), entry.get(),
+ [](const ResourceEntry* a, const ResourceEntry* b) -> bool {
+ return a->entryId < b->entryId;
+ });
+ sortedEntries.insert(iter, entry.get());
+ }
+
+ for (const ResourceEntry* entry : sortedEntries) {
+ ResourceId id = { table->getPackageId(), type->typeId, entry->entryId };
+ ResourceName name = { table->getPackage(), type->type, entry->name };
+ std::cout << " spec resource " << id << " " << name;
+ if (entry->publicStatus.isPublic) {
+ std::cout << " PUBLIC";
+ }
+ std::cout << std::endl;
+
+ PrintVisitor visitor;
+ for (const auto& value : entry->values) {
+ std::cout << " (" << value.config << ") ";
+ value.value->accept(visitor, {});
+ 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);
+}
+
+void Debug::printStyleGraph(const std::shared_ptr<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;
+ }
+
+ const ResourceTableType* type;
+ const ResourceEntry* entry;
+ std::tie(type, entry) = table->findResource(styleName);
+ if (entry) {
+ for (const auto& value : entry->values) {
+ visitFunc<Style>(*value.value, [&](const Style& style) {
+ if (style.parent.name.isValid()) {
+ parents.insert(style.parent.name);
+ stylesToVisit.push(style.parent.name);
+ }
+ });
+ }
+ }
+ }
+
+ 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& styleName = entry.first;
+ size_t styleNodeIndex = getNodeIndex(names, styleName);
+
+ for (const auto& parentName : entry.second) {
+ std::cout << " node_" << styleNodeIndex << " -> "
+ << "node_" << getNodeIndex(names, parentName) << ";\n";
+ }
+ }
+
+ std::cout << "}" << std::endl;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
new file mode 100644
index 0000000..cdb3dcb
--- /dev/null
+++ b/tools/aapt2/Debug.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_DEBUG_H
+#define AAPT_DEBUG_H
+
+#include "Resource.h"
+#include "ResourceTable.h"
+
+#include <memory>
+
+namespace aapt {
+
+struct Debug {
+ static void printTable(const std::shared_ptr<ResourceTable>& table);
+ static void printStyleGraph(const std::shared_ptr<ResourceTable>& table,
+ const ResourceName& targetStyle);
+};
+
+} // namespace aapt
+
+#endif // AAPT_DEBUG_H
diff --git a/tools/aapt2/Files.cpp b/tools/aapt2/Files.cpp
new file mode 100644
index 0000000..8484148
--- /dev/null
+++ b/tools/aapt2/Files.cpp
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Files.h"
+#include "Util.h"
+
+#include <cerrno>
+#include <dirent.h>
+#include <string>
+#include <sys/stat.h>
+
+#ifdef HAVE_MS_C_RUNTIME
+// Windows includes.
+#include <direct.h>
+#endif
+
+namespace aapt {
+
+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 defined(S_ISLNK)
+ } 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;
+#endif
+ } else {
+ return FileType::kUnknown;
+ }
+}
+
+std::vector<std::string> listFiles(const StringPiece& root) {
+ DIR* dir = opendir(root.data());
+ if (dir == nullptr) {
+ Logger::error(Source{ root.toString() })
+ << "unable to open file: "
+ << strerror(errno)
+ << "."
+ << std::endl;
+ return {};
+ }
+
+ std::vector<std::string> files;
+ dirent* entry;
+ while ((entry = readdir(dir))) {
+ files.emplace_back(entry->d_name);
+ }
+
+ closedir(dir);
+ return files;
+}
+
+inline static int mkdirImpl(const StringPiece& path) {
+#ifdef HAVE_MS_C_RUNTIME
+ return _mkdir(path.toString().c_str());
+#else
+ 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) {
+ StringPiece parentPath(start, current - start);
+ int result = mkdirImpl(parentPath);
+ if (result < 0 && errno != EEXIST) {
+ return false;
+ }
+ }
+ }
+ return mkdirImpl(path) == 0 || errno == EEXIST;
+}
+
+std::string getStem(const StringPiece& path) {
+ const char* start = path.begin();
+ const char* end = path.end();
+ for (const char* current = end - 1; current != start - 1; --current) {
+ if (*current == sDirSep) {
+ return std::string(start, current - start);
+ }
+ }
+ return {};
+}
+
+bool FileFilter::setPattern(const StringPiece& pattern) {
+ mPatternTokens = util::splitAndLowercase(pattern, ':');
+ return true;
+}
+
+bool FileFilter::operator()(const std::string& filename, FileType type) const {
+ 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++;
+ }
+
+ if (strncasecmp(tokenStr, kDir, sizeof(kDir)) == 0) {
+ if (type != FileType::kDirectory) {
+ continue;
+ }
+ tokenStr += sizeof(kDir);
+ }
+
+ if (strncasecmp(tokenStr, kFile, sizeof(kFile)) == 0) {
+ if (type != FileType::kRegular) {
+ continue;
+ }
+ tokenStr += 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;
+ }
+
+ if (ignore) {
+ if (chatty) {
+ Logger::warn()
+ << "skipping " <<
+ (type == FileType::kDirectory ? "dir '" : "file '")
+ << filename
+ << "' due to ignore pattern '"
+ << token
+ << "'."
+ << std::endl;
+ }
+ return false;
+ }
+ }
+ return true;
+}
+
+
+} // namespace aapt
diff --git a/tools/aapt2/Files.h b/tools/aapt2/Files.h
new file mode 100644
index 0000000..844fd2b
--- /dev/null
+++ b/tools/aapt2/Files.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_FILES_H
+#define AAPT_FILES_H
+
+#include "Logger.h"
+#include "Source.h"
+#include "StringPiece.h"
+
+#include <cassert>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+#ifdef _WIN32
+constexpr const char sDirSep = '\\';
+#else
+constexpr const char sDirSep = '/';
+#endif
+
+enum class FileType {
+ 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);
+
+/*
+ * Appends a path to `base`, separated by the directory separator.
+ */
+void appendPath(std::string* base, const StringPiece& part);
+
+/*
+ * Appends a series of paths to `base`, separated by the
+ * system directory separator.
+ */
+template <typename... Ts >
+void appendPath(std::string* base, const StringPiece& part, const Ts&... parts);
+
+/*
+ * Makes all the directories in `path`. The last element in the path
+ * is interpreted as a directory.
+ */
+bool mkdirs(const StringPiece& path);
+
+/**
+ * Returns all but the last part of the path.
+ */
+std::string getStem(const StringPiece& path);
+
+/*
+ * 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.
+ */
+class FileFilter {
+public:
+ /*
+ * 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:
+ std::vector<std::string> mPatternTokens;
+};
+
+inline void appendPath(std::string* base, const StringPiece& part) {
+ assert(base);
+ *base += sDirSep;
+ base->append(part.data(), part.size());
+}
+
+template <typename... Ts >
+void appendPath(std::string* base, const StringPiece& part, const Ts&... parts) {
+ assert(base);
+ *base += sDirSep;
+ base->append(part.data(), part.size());
+ appendPath(base, parts...);
+}
+
+} // namespace aapt
+
+#endif // AAPT_FILES_H
diff --git a/tools/aapt2/Flag.cpp b/tools/aapt2/Flag.cpp
new file mode 100644
index 0000000..76985da
--- /dev/null
+++ b/tools/aapt2/Flag.cpp
@@ -0,0 +1,132 @@
+#include "Flag.h"
+#include "StringPiece.h"
+
+#include <functional>
+#include <iomanip>
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace aapt {
+namespace flag {
+
+struct Flag {
+ std::string name;
+ std::string description;
+ std::function<bool(const StringPiece&, std::string*)> action;
+ bool required;
+ bool* flagResult;
+ bool flagValueWhenSet;
+ bool parsed;
+};
+
+static std::vector<Flag> sFlags;
+static std::vector<std::string> sArgs;
+
+static std::function<bool(const StringPiece&, std::string*)> wrap(
+ const std::function<void(const StringPiece&)>& action) {
+ return [action](const StringPiece& arg, std::string*) -> bool {
+ action(arg);
+ return true;
+ };
+}
+
+void optionalFlag(const StringPiece& name, const StringPiece& description,
+ std::function<void(const StringPiece&)> action) {
+ sFlags.push_back(Flag{
+ name.toString(), description.toString(), wrap(action),
+ false, nullptr, false, false });
+}
+
+void requiredFlag(const StringPiece& name, const StringPiece& description,
+ std::function<void(const StringPiece&)> action) {
+ sFlags.push_back(Flag{ name.toString(), description.toString(), wrap(action),
+ true, nullptr, false, false });
+}
+
+void requiredFlag(const StringPiece& name, const StringPiece& description,
+ std::function<bool(const StringPiece&, std::string*)> action) {
+ sFlags.push_back(Flag{ name.toString(), description.toString(), action,
+ true, nullptr, false, false });
+}
+
+void optionalSwitch(const StringPiece& name, const StringPiece& description, bool resultWhenSet,
+ bool* result) {
+ sFlags.push_back(Flag{
+ name.toString(), description.toString(), {},
+ false, result, resultWhenSet, false });
+}
+
+void usageAndDie(const StringPiece& command) {
+ std::cerr << command << " [options]";
+ for (const Flag& flag : sFlags) {
+ if (flag.required) {
+ std::cerr << " " << flag.name << " arg";
+ }
+ }
+ std::cerr << " files..." << std::endl << std::endl << "Options:" << std::endl;
+
+ for (const Flag& flag : sFlags) {
+ std::string command = flag.name;
+ if (!flag.flagResult) {
+ command += " arg ";
+ }
+ std::cerr << " " << std::setw(30) << std::left << command
+ << flag.description << std::endl;
+ }
+ exit(1);
+}
+
+void parse(int argc, char** argv, const StringPiece& command) {
+ std::string errorStr;
+ for (int i = 0; i < argc; i++) {
+ const StringPiece arg(argv[i]);
+ if (*arg.data() != '-') {
+ sArgs.push_back(arg.toString());
+ continue;
+ }
+
+ bool match = false;
+ for (Flag& flag : sFlags) {
+ if (arg == flag.name) {
+ match = true;
+ flag.parsed = true;
+ if (flag.flagResult) {
+ *flag.flagResult = flag.flagValueWhenSet;
+ } else {
+ i++;
+ if (i >= argc) {
+ std::cerr << flag.name << " missing argument." << std::endl
+ << std::endl;
+ usageAndDie(command);
+ }
+
+ if (!flag.action(argv[i], &errorStr)) {
+ std::cerr << errorStr << "." << std::endl << std::endl;
+ usageAndDie(command);
+ }
+ }
+ break;
+ }
+ }
+
+ if (!match) {
+ std::cerr << "unknown option '" << arg << "'." << std::endl << std::endl;
+ usageAndDie(command);
+ }
+ }
+
+ for (const Flag& flag : sFlags) {
+ if (flag.required && !flag.parsed) {
+ std::cerr << "missing required flag " << flag.name << std::endl << std::endl;
+ usageAndDie(command);
+ }
+ }
+}
+
+const std::vector<std::string>& getArgs() {
+ return sArgs;
+}
+
+} // namespace flag
+} // namespace aapt
diff --git a/tools/aapt2/Flag.h b/tools/aapt2/Flag.h
new file mode 100644
index 0000000..e863742
--- /dev/null
+++ b/tools/aapt2/Flag.h
@@ -0,0 +1,34 @@
+#ifndef AAPT_FLAG_H
+#define AAPT_FLAG_H
+
+#include "StringPiece.h"
+
+#include <functional>
+#include <string>
+#include <vector>
+
+namespace aapt {
+namespace flag {
+
+void requiredFlag(const StringPiece& name, const StringPiece& description,
+ std::function<void(const StringPiece&)> action);
+
+void requiredFlag(const StringPiece& name, const StringPiece& description,
+ std::function<bool(const StringPiece&, std::string*)> action);
+
+void optionalFlag(const StringPiece& name, const StringPiece& description,
+ std::function<void(const StringPiece&)> action);
+
+void optionalSwitch(const StringPiece& name, const StringPiece& description, bool resultWhenSet,
+ bool* result);
+
+void usageAndDie(const StringPiece& command);
+
+void parse(int argc, char** argv, const StringPiece& command);
+
+const std::vector<std::string>& getArgs();
+
+} // namespace flag
+} // namespace aapt
+
+#endif // AAPT_FLAG_H
diff --git a/tools/aapt2/JavaClassGenerator.cpp b/tools/aapt2/JavaClassGenerator.cpp
new file mode 100644
index 0000000..e2ffe79
--- /dev/null
+++ b/tools/aapt2/JavaClassGenerator.cpp
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "JavaClassGenerator.h"
+#include "NameMangler.h"
+#include "Resource.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "StringPiece.h"
+
+#include <algorithm>
+#include <ostream>
+#include <set>
+#include <sstream>
+#include <tuple>
+
+namespace aapt {
+
+// The number of attributes to emit per line in a Styleable array.
+constexpr size_t kAttribsPerLine = 4;
+
+JavaClassGenerator::JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table,
+ Options options) :
+ mTable(table), mOptions(options) {
+}
+
+static void generateHeader(std::ostream& out, const StringPiece16& package) {
+ out << "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
+ " *\n"
+ " * This class was automatically generated by the\n"
+ " * aapt tool from the resource data it found. It\n"
+ " * should not be modified by hand.\n"
+ " */\n\n";
+ out << "package " << package << ";"
+ << std::endl
+ << std::endl;
+}
+
+static const std::set<StringPiece16> sJavaIdentifiers = {
+ u"abstract", u"assert", u"boolean", u"break", u"byte",
+ u"case", u"catch", u"char", u"class", u"const", u"continue",
+ u"default", u"do", u"double", u"else", u"enum", u"extends",
+ u"final", u"finally", u"float", u"for", u"goto", u"if",
+ u"implements", u"import", u"instanceof", u"int", u"interface",
+ u"long", u"native", u"new", u"package", u"private", u"protected",
+ u"public", u"return", u"short", u"static", u"strictfp", u"super",
+ u"switch", u"synchronized", u"this", u"throw", u"throws",
+ u"transient", u"try", u"void", u"volatile", u"while", u"true",
+ u"false", u"null"
+};
+
+static bool isValidSymbol(const StringPiece16& symbol) {
+ return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end();
+}
+
+/*
+ * Java symbols can not contain . or -, but those are valid in a resource name.
+ * Replace those with '_'.
+ */
+static std::u16string transform(const StringPiece16& symbol) {
+ std::u16string output = symbol.toString();
+ for (char16_t& c : output) {
+ if (c == u'.' || c == u'-') {
+ c = u'_';
+ }
+ }
+ return output;
+}
+
+struct GenArgs : ValueVisitorArgs {
+ GenArgs(std::ostream* o, const std::u16string* p, std::u16string* e) :
+ out(o), package(p), entryName(e) {
+ }
+
+ std::ostream* out;
+ const std::u16string* package;
+ std::u16string* entryName;
+};
+
+void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a) {
+ const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
+ std::ostream* out = static_cast<GenArgs&>(a).out;
+ const std::u16string* package = static_cast<GenArgs&>(a).package;
+ std::u16string* entryName = static_cast<GenArgs&>(a).entryName;
+
+ // This must be sorted by resource ID.
+ std::vector<std::pair<ResourceId, ResourceNameRef>> sortedAttributes;
+ sortedAttributes.reserve(styleable.entries.size());
+ for (const auto& attr : styleable.entries) {
+ // If we are not encoding final attributes, the styleable entry may have no ID
+ // if we are building a static library.
+ assert((!mOptions.useFinal || attr.id.isValid()) && "no ID set for Styleable entry");
+ assert(attr.name.isValid() && "no name set for Styleable entry");
+ sortedAttributes.emplace_back(attr.id, attr.name);
+ }
+ std::sort(sortedAttributes.begin(), sortedAttributes.end());
+
+ // First we emit the array containing the IDs of each attribute.
+ *out << " "
+ << "public static final int[] " << transform(*entryName) << " = {";
+
+ const size_t attrCount = sortedAttributes.size();
+ for (size_t i = 0; i < attrCount; i++) {
+ if (i % kAttribsPerLine == 0) {
+ *out << std::endl << " ";
+ }
+
+ *out << sortedAttributes[i].first;
+ if (i != attrCount - 1) {
+ *out << ", ";
+ }
+ }
+ *out << std::endl << " };" << std::endl;
+
+ // Now we emit the indices into the array.
+ for (size_t i = 0; i < attrCount; i++) {
+ *out << " "
+ << "public static" << finalModifier
+ << " int " << transform(*entryName);
+
+ // We may reference IDs from other packages, so prefix the entry name with
+ // the package.
+ const ResourceNameRef& itemName = sortedAttributes[i].second;
+ if (itemName.package != *package) {
+ *out << "_" << transform(itemName.package);
+ }
+ *out << "_" << transform(itemName.entry) << " = " << i << ";" << std::endl;
+ }
+}
+
+bool JavaClassGenerator::generateType(const std::u16string& package, size_t packageId,
+ const ResourceTableType& type, std::ostream& out) {
+ const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
+
+ std::u16string unmangledPackage;
+ std::u16string unmangledName;
+ for (const auto& entry : type.entries) {
+ ResourceId id = { packageId, type.typeId, entry->entryId };
+ assert(id.isValid());
+
+ unmangledName = entry->name;
+ if (NameMangler::unmangle(&unmangledName, &unmangledPackage)) {
+ // The entry name was mangled, and we successfully unmangled it.
+ // Check that we want to emit this symbol.
+ if (package != unmangledPackage) {
+ // Skip the entry if it doesn't belong to the package we're writing.
+ continue;
+ }
+ } else {
+ if (package != mTable->getPackage()) {
+ // We are processing a mangled package name,
+ // but this is a non-mangled resource.
+ continue;
+ }
+ }
+
+ if (!isValidSymbol(unmangledName)) {
+ ResourceNameRef resourceName = { package, type.type, unmangledName };
+ std::stringstream err;
+ err << "invalid symbol name '" << resourceName << "'";
+ mError = err.str();
+ return false;
+ }
+
+ if (type.type == ResourceType::kStyleable) {
+ assert(!entry->values.empty());
+ entry->values.front().value->accept(*this, GenArgs{ &out, &package, &unmangledName });
+ } else {
+ out << " " << "public static" << finalModifier
+ << " int " << transform(unmangledName) << " = " << id << ";" << std::endl;
+ }
+ }
+ return true;
+}
+
+bool JavaClassGenerator::generate(const std::u16string& package, std::ostream& out) {
+ const size_t packageId = mTable->getPackageId();
+
+ generateHeader(out, package);
+
+ out << "public final class R {" << std::endl;
+
+ for (const auto& type : *mTable) {
+ out << " public static final class " << type->type << " {" << std::endl;
+ if (!generateType(package, packageId, *type, out)) {
+ return false;
+ }
+ out << " }" << std::endl;
+ }
+
+ out << "}" << std::endl;
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/JavaClassGenerator.h b/tools/aapt2/JavaClassGenerator.h
new file mode 100644
index 0000000..f8b9ee3
--- /dev/null
+++ b/tools/aapt2/JavaClassGenerator.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_JAVA_CLASS_GENERATOR_H
+#define AAPT_JAVA_CLASS_GENERATOR_H
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+
+#include <ostream>
+#include <string>
+
+namespace aapt {
+
+/*
+ * Generates the R.java file for a resource table.
+ */
+class JavaClassGenerator : ConstValueVisitor {
+public:
+ /*
+ * A set of options for this JavaClassGenerator.
+ */
+ struct Options {
+ /*
+ * Specifies whether to use the 'final' modifier
+ * on resource entries. Default is true.
+ */
+ bool useFinal = true;
+ };
+
+ JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table, Options options);
+
+ /*
+ * Writes the R.java file to `out`. Only symbols belonging to `package` are written.
+ * All symbols technically belong to a single package, but linked libraries will
+ * have their names mangled, denoting that they came from a different package.
+ * We need to generate these symbols in a separate file.
+ * Returns true on success.
+ */
+ bool generate(const std::u16string& package, std::ostream& out);
+
+ /*
+ * ConstValueVisitor implementation.
+ */
+ void visit(const Styleable& styleable, ValueVisitorArgs& args);
+
+ const std::string& getError() const;
+
+private:
+ bool generateType(const std::u16string& package, size_t packageId,
+ const ResourceTableType& type, std::ostream& out);
+
+ std::shared_ptr<const ResourceTable> mTable;
+ Options mOptions;
+ std::string mError;
+};
+
+inline const std::string& JavaClassGenerator::getError() const {
+ return mError;
+}
+
+} // namespace aapt
+
+#endif // AAPT_JAVA_CLASS_GENERATOR_H
diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/JavaClassGenerator_test.cpp
new file mode 100644
index 0000000..b385ff4
--- /dev/null
+++ b/tools/aapt2/JavaClassGenerator_test.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "JavaClassGenerator.h"
+#include "Linker.h"
+#include "MockResolver.h"
+#include "ResourceTable.h"
+#include "ResourceTableResolver.h"
+#include "ResourceValues.h"
+#include "Util.h"
+
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+struct JavaClassGeneratorTest : public ::testing::Test {
+ virtual void SetUp() override {
+ mTable = std::make_shared<ResourceTable>();
+ mTable->setPackage(u"android");
+ mTable->setPackageId(0x01);
+ }
+
+ bool addResource(const ResourceNameRef& name, ResourceId id) {
+ return mTable->addResource(name, id, {}, SourceLine{ "test.xml", 21 },
+ util::make_unique<Id>());
+ }
+
+ std::shared_ptr<ResourceTable> mTable;
+};
+
+TEST_F(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) {
+ ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"class" },
+ ResourceId{ 0x01, 0x02, 0x0000 }));
+
+ JavaClassGenerator generator(mTable, {});
+
+ std::stringstream out;
+ EXPECT_FALSE(generator.generate(mTable->getPackage(), out));
+}
+
+TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
+ ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"hey-man" },
+ ResourceId{ 0x01, 0x02, 0x0000 }));
+
+ ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kAttr, u"cool.attr" },
+ ResourceId{ 0x01, 0x01, 0x0000 }));
+
+ std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
+ Reference ref(ResourceName{ u"android", ResourceType::kAttr, u"cool.attr"});
+ ref.id = ResourceId{ 0x01, 0x01, 0x0000 };
+ styleable->entries.emplace_back(ref);
+
+ ASSERT_TRUE(mTable->addResource(ResourceName{ {}, ResourceType::kStyleable, u"hey.dude" },
+ ResourceId{ 0x01, 0x03, 0x0000 }, {},
+ SourceLine{ "test.xml", 21 }, std::move(styleable)));
+
+ JavaClassGenerator generator(mTable, {});
+
+ std::stringstream out;
+ EXPECT_TRUE(generator.generate(mTable->getPackage(), out));
+ std::string output = out.str();
+
+ EXPECT_NE(std::string::npos,
+ output.find("public static final int hey_man = 0x01020000;"));
+
+ EXPECT_NE(std::string::npos,
+ output.find("public static final int[] hey_dude = {"));
+
+ EXPECT_NE(std::string::npos,
+ output.find("public static final int hey_dude_cool_attr = 0;"));
+}
+
+
+TEST_F(JavaClassGeneratorTest, EmitPackageMangledSymbols) {
+ ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"foo" },
+ ResourceId{ 0x01, 0x02, 0x0000 }));
+ ResourceTable table;
+ table.setPackage(u"com.lib");
+ ASSERT_TRUE(table.addResource(ResourceName{ {}, ResourceType::kId, u"test" }, {},
+ SourceLine{ "lib.xml", 33 }, util::make_unique<Id>()));
+ ASSERT_TRUE(mTable->merge(std::move(table)));
+
+ Linker linker(mTable,
+ std::make_shared<MockResolver>(mTable, std::map<ResourceName, ResourceId>()),
+ {});
+ ASSERT_TRUE(linker.linkAndValidate());
+
+ JavaClassGenerator generator(mTable, {});
+
+ std::stringstream out;
+ EXPECT_TRUE(generator.generate(mTable->getPackage(), out));
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("int foo ="));
+ EXPECT_EQ(std::string::npos, output.find("int test ="));
+
+ out.str("");
+ EXPECT_TRUE(generator.generate(u"com.lib", out));
+ output = out.str();
+ EXPECT_NE(std::string::npos, output.find("int test ="));
+ EXPECT_EQ(std::string::npos, output.find("int foo ="));
+}
+
+TEST_F(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) {
+ std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
+ styleable->entries.emplace_back(ResourceNameRef{ mTable->getPackage(),
+ ResourceType::kAttr,
+ u"bar" });
+ styleable->entries.emplace_back(ResourceNameRef{ u"com.lib", ResourceType::kAttr, u"bar" });
+ ASSERT_TRUE(mTable->addResource(ResourceName{ {}, ResourceType::kStyleable, u"Foo" }, {}, {},
+ std::move(styleable)));
+
+ std::shared_ptr<IResolver> resolver = std::make_shared<MockResolver>(mTable,
+ std::map<ResourceName, ResourceId>({
+ { ResourceName{ u"android", ResourceType::kAttr, u"bar" },
+ ResourceId{ 0x01, 0x01, 0x0000 } },
+ { ResourceName{ u"com.lib", ResourceType::kAttr, u"bar" },
+ ResourceId{ 0x02, 0x01, 0x0000 } }}));
+
+ Linker linker(mTable, resolver, {});
+ ASSERT_TRUE(linker.linkAndValidate());
+
+ JavaClassGenerator generator(mTable, {});
+
+ std::stringstream out;
+ EXPECT_TRUE(generator.generate(mTable->getPackage(), out));
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("int Foo_bar ="));
+ EXPECT_NE(std::string::npos, output.find("int Foo_com_lib_bar ="));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Linker.cpp b/tools/aapt2/Linker.cpp
new file mode 100644
index 0000000..f3f04a5
--- /dev/null
+++ b/tools/aapt2/Linker.cpp
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Linker.h"
+#include "Logger.h"
+#include "NameMangler.h"
+#include "Resolver.h"
+#include "ResourceParser.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "StringPiece.h"
+#include "Util.h"
+
+#include <androidfw/AssetManager.h>
+#include <array>
+#include <bitset>
+#include <iostream>
+#include <map>
+#include <ostream>
+#include <set>
+#include <sstream>
+#include <tuple>
+#include <vector>
+
+namespace aapt {
+
+Linker::Args::Args(const ResourceNameRef& r, const SourceLine& s) : referrer(r), source(s) {
+}
+
+Linker::Linker(const std::shared_ptr<ResourceTable>& table,
+ const std::shared_ptr<IResolver>& resolver, const Options& options) :
+ mResolver(resolver), mTable(table), mOptions(options), mError(false) {
+}
+
+bool Linker::linkAndValidate() {
+ std::bitset<256> usedTypeIds;
+ std::array<std::set<uint16_t>, 256> usedIds;
+ usedTypeIds.set(0);
+
+ // Collect which resource IDs are already taken.
+ for (auto& type : *mTable) {
+ if (type->typeId != ResourceTableType::kUnsetTypeId) {
+ // The ID for this type has already been set. We
+ // mark this ID as taken so we don't re-assign it
+ // later.
+ usedTypeIds.set(type->typeId);
+ }
+
+ for (auto& entry : type->entries) {
+ if (type->typeId != ResourceTableType::kUnsetTypeId &&
+ entry->entryId != ResourceEntry::kUnsetEntryId) {
+ // The ID for this entry has already been set. We
+ // mark this ID as taken so we don't re-assign it
+ // later.
+ usedIds[type->typeId].insert(entry->entryId);
+ }
+ }
+ }
+
+ // Assign resource IDs that are available.
+ size_t nextTypeIndex = 0;
+ for (auto& type : *mTable) {
+ if (type->typeId == ResourceTableType::kUnsetTypeId) {
+ while (nextTypeIndex < usedTypeIds.size() && usedTypeIds[nextTypeIndex]) {
+ nextTypeIndex++;
+ }
+ type->typeId = nextTypeIndex++;
+ }
+
+ const auto endEntryIter = std::end(usedIds[type->typeId]);
+ auto nextEntryIter = std::begin(usedIds[type->typeId]);
+ size_t nextIndex = 0;
+ for (auto& entry : type->entries) {
+ if (entry->entryId == ResourceTableType::kUnsetTypeId) {
+ while (nextEntryIter != endEntryIter &&
+ nextIndex == *nextEntryIter) {
+ nextIndex++;
+ ++nextEntryIter;
+ }
+ entry->entryId = nextIndex++;
+ }
+ }
+ }
+
+ // Now do reference linking.
+ for (auto& type : *mTable) {
+ for (auto& entry : type->entries) {
+ if (entry->publicStatus.isPublic && entry->values.empty()) {
+ // A public resource has no values. It will not be encoded
+ // properly without a symbol table. This is a unresolved symbol.
+ addUnresolvedSymbol(ResourceNameRef{
+ mTable->getPackage(), type->type, entry->name },
+ entry->publicStatus.source);
+ continue;
+ }
+
+ for (auto& valueConfig : entry->values) {
+ // Dispatch to the right method of this linker
+ // based on the value's type.
+ valueConfig.value->accept(*this, Args{
+ ResourceNameRef{ mTable->getPackage(), type->type, entry->name },
+ valueConfig.source
+ });
+ }
+ }
+ }
+ return !mError;
+}
+
+const Linker::ResourceNameToSourceMap& Linker::getUnresolvedReferences() const {
+ return mUnresolvedSymbols;
+}
+
+void Linker::doResolveReference(Reference& reference, const SourceLine& source) {
+ Maybe<ResourceId> result = mResolver->findId(reference.name);
+ if (!result) {
+ addUnresolvedSymbol(reference.name, source);
+ return;
+ }
+ assert(result.value().isValid());
+
+ if (mOptions.linkResourceIds) {
+ reference.id = result.value();
+ } else {
+ reference.id = 0;
+ }
+}
+
+const Attribute* Linker::doResolveAttribute(Reference& attribute, const SourceLine& source) {
+ Maybe<IResolver::Entry> result = mResolver->findAttribute(attribute.name);
+ if (!result || !result.value().attr) {
+ addUnresolvedSymbol(attribute.name, source);
+ return nullptr;
+ }
+
+ const IResolver::Entry& entry = result.value();
+ assert(entry.id.isValid());
+
+ if (mOptions.linkResourceIds) {
+ attribute.id = entry.id;
+ } else {
+ attribute.id = 0;
+ }
+ return entry.attr;
+}
+
+void Linker::visit(Reference& reference, ValueVisitorArgs& a) {
+ Args& args = static_cast<Args&>(a);
+
+ if (!reference.name.isValid()) {
+ // We can't have a completely bad reference.
+ if (!reference.id.isValid()) {
+ Logger::error() << "srsly? " << args.referrer << std::endl;
+ assert(reference.id.isValid());
+ }
+
+ // This reference has no name but has an ID.
+ // It is a really bad error to have no name and have the same
+ // package ID.
+ assert(reference.id.packageId() != mTable->getPackageId());
+
+ // The reference goes outside this package, let it stay as a
+ // resource ID because it will not change.
+ return;
+ }
+
+ doResolveReference(reference, args.source);
+
+ // TODO(adamlesinski): Verify the referencedType is another reference
+ // or a compatible primitive.
+}
+
+void Linker::processAttributeValue(const ResourceNameRef& name, const SourceLine& source,
+ const Attribute& attr, std::unique_ptr<Item>& value) {
+ std::unique_ptr<Item> convertedValue;
+ visitFunc<RawString>(*value, [&](RawString& str) {
+ // This is a raw string, so check if it can be converted to anything.
+ // We can NOT swap value with the converted value in here, since
+ // we called through the original value.
+
+ auto onCreateReference = [&](const ResourceName& name) {
+ // We should never get here. All references would have been
+ // parsed in the parser phase.
+ assert(false);
+ };
+
+ convertedValue = ResourceParser::parseItemForAttribute(*str.value, attr,
+ onCreateReference);
+ if (!convertedValue && attr.typeMask & android::ResTable_map::TYPE_STRING) {
+ // Last effort is to parse as a string.
+ util::StringBuilder builder;
+ builder.append(*str.value);
+ if (builder) {
+ convertedValue = util::make_unique<String>(
+ mTable->getValueStringPool().makeRef(builder.str()));
+ }
+ }
+ });
+
+ if (convertedValue) {
+ value = std::move(convertedValue);
+ }
+
+ // Process this new or old value (it can be a reference!).
+ value->accept(*this, Args{ name, source });
+
+ // Flatten the value to see what resource type it is.
+ android::Res_value resValue;
+ value->flatten(resValue);
+
+ // Always allow references.
+ const uint32_t typeMask = attr.typeMask | android::ResTable_map::TYPE_REFERENCE;
+ if (!(typeMask & ResourceParser::androidTypeToAttributeTypeMask(resValue.dataType))) {
+ Logger::error(source)
+ << *value
+ << " is not compatible with attribute "
+ << attr
+ << "."
+ << std::endl;
+ mError = true;
+ }
+}
+
+void Linker::visit(Style& style, ValueVisitorArgs& a) {
+ Args& args = static_cast<Args&>(a);
+
+ if (style.parent.name.isValid() || style.parent.id.isValid()) {
+ visit(style.parent, a);
+ }
+
+ for (Style::Entry& styleEntry : style.entries) {
+ const Attribute* attr = doResolveAttribute(styleEntry.key, args.source);
+ if (attr) {
+ processAttributeValue(args.referrer, args.source, *attr, styleEntry.value);
+ }
+ }
+}
+
+void Linker::visit(Attribute& attr, ValueVisitorArgs& a) {
+ static constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM |
+ android::ResTable_map::TYPE_FLAGS;
+ if (attr.typeMask & kMask) {
+ for (auto& symbol : attr.symbols) {
+ visit(symbol.symbol, a);
+ }
+ }
+}
+
+void Linker::visit(Styleable& styleable, ValueVisitorArgs& a) {
+ for (auto& attrRef : styleable.entries) {
+ visit(attrRef, a);
+ }
+}
+
+void Linker::visit(Array& array, ValueVisitorArgs& a) {
+ Args& args = static_cast<Args&>(a);
+
+ for (auto& item : array.items) {
+ item->accept(*this, Args{ args.referrer, args.source });
+ }
+}
+
+void Linker::visit(Plural& plural, ValueVisitorArgs& a) {
+ Args& args = static_cast<Args&>(a);
+
+ for (auto& item : plural.values) {
+ if (item) {
+ item->accept(*this, Args{ args.referrer, args.source });
+ }
+ }
+}
+
+void Linker::addUnresolvedSymbol(const ResourceNameRef& name, const SourceLine& source) {
+ mUnresolvedSymbols[name.toResourceName()].push_back(source);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Linker.h b/tools/aapt2/Linker.h
new file mode 100644
index 0000000..6f03515
--- /dev/null
+++ b/tools/aapt2/Linker.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_LINKER_H
+#define AAPT_LINKER_H
+
+#include "Resolver.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "Source.h"
+#include "StringPiece.h"
+
+#include <androidfw/AssetManager.h>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <vector>
+
+namespace aapt {
+
+/**
+ * The Linker has two jobs. It follows resource references
+ * and verifies that their targert exists and that their
+ * types are compatible. The Linker will also assign resource
+ * IDs and fill in all the dependent references with the newly
+ * assigned resource IDs.
+ *
+ * To do this, the Linker builds a graph of references. This
+ * can be useful to do other analysis, like building a
+ * dependency graph of source files. The hope is to be able to
+ * add functionality that operates on the graph without
+ * overcomplicating the Linker.
+ *
+ * TODO(adamlesinski): Build the graph first then run the separate
+ * steps over the graph.
+ */
+class Linker : ValueVisitor {
+public:
+ struct Options {
+ /**
+ * Assign resource Ids to references when linking.
+ * When building a static library, set this to false.
+ */
+ bool linkResourceIds = true;
+ };
+
+ /**
+ * Create a Linker for the given resource table with the sources available in
+ * IResolver. IResolver should contain the ResourceTable as a source too.
+ */
+ Linker(const std::shared_ptr<ResourceTable>& table,
+ const std::shared_ptr<IResolver>& resolver, const Options& options);
+
+ Linker(const Linker&) = delete;
+
+ virtual ~Linker() = default;
+
+ /**
+ * Entry point to the linker. Assigns resource IDs, follows references,
+ * and validates types. Returns true if all references to defined values
+ * are type-compatible. Missing resource references are recorded but do
+ * not cause this method to fail.
+ */
+ bool linkAndValidate();
+
+ /**
+ * Returns any references to resources that were not defined in any of the
+ * sources.
+ */
+ using ResourceNameToSourceMap = std::map<ResourceName, std::vector<SourceLine>>;
+ const ResourceNameToSourceMap& getUnresolvedReferences() const;
+
+protected:
+ virtual void doResolveReference(Reference& reference, const SourceLine& source);
+ virtual const Attribute* doResolveAttribute(Reference& attribute, const SourceLine& source);
+
+ std::shared_ptr<IResolver> mResolver;
+
+private:
+ struct Args : public ValueVisitorArgs {
+ Args(const ResourceNameRef& r, const SourceLine& s);
+
+ const ResourceNameRef& referrer;
+ const SourceLine& source;
+ };
+
+ //
+ // Overrides of ValueVisitor
+ //
+ void visit(Reference& reference, ValueVisitorArgs& args) override;
+ void visit(Attribute& attribute, ValueVisitorArgs& args) override;
+ void visit(Styleable& styleable, ValueVisitorArgs& args) override;
+ void visit(Style& style, ValueVisitorArgs& args) override;
+ void visit(Array& array, ValueVisitorArgs& args) override;
+ void visit(Plural& plural, ValueVisitorArgs& args) override;
+
+ void processAttributeValue(const ResourceNameRef& name, const SourceLine& source,
+ const Attribute& attr, std::unique_ptr<Item>& value);
+
+ void addUnresolvedSymbol(const ResourceNameRef& name, const SourceLine& source);
+
+ std::shared_ptr<ResourceTable> mTable;
+ std::map<ResourceName, std::vector<SourceLine>> mUnresolvedSymbols;
+ Options mOptions;
+ bool mError;
+};
+
+} // namespace aapt
+
+#endif // AAPT_LINKER_H
diff --git a/tools/aapt2/Linker_test.cpp b/tools/aapt2/Linker_test.cpp
new file mode 100644
index 0000000..d897f98
--- /dev/null
+++ b/tools/aapt2/Linker_test.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Linker.h"
+#include "ResourceTable.h"
+#include "ResourceTableResolver.h"
+#include "ResourceValues.h"
+#include "Util.h"
+
+#include <androidfw/AssetManager.h>
+#include <gtest/gtest.h>
+#include <string>
+
+namespace aapt {
+
+struct LinkerTest : public ::testing::Test {
+ virtual void SetUp() override {
+ mTable = std::make_shared<ResourceTable>();
+ mTable->setPackage(u"android");
+ mTable->setPackageId(0x01);
+ mLinker = std::make_shared<Linker>(mTable, std::make_shared<ResourceTableResolver>(
+ mTable, std::vector<std::shared_ptr<const android::AssetManager>>()),
+ Linker::Options{});
+
+ // Create a few attributes for use in the tests.
+
+ addResource(ResourceName{ {}, ResourceType::kAttr, u"integer" },
+ util::make_unique<Attribute>(false, android::ResTable_map::TYPE_INTEGER));
+
+ addResource(ResourceName{ {}, ResourceType::kAttr, u"string" },
+ util::make_unique<Attribute>(false, android::ResTable_map::TYPE_STRING));
+
+ addResource(ResourceName{ {}, ResourceType::kId, u"apple" }, util::make_unique<Id>());
+
+ addResource(ResourceName{ {}, ResourceType::kId, u"banana" }, util::make_unique<Id>());
+
+ std::unique_ptr<Attribute> flagAttr = util::make_unique<Attribute>(
+ false, android::ResTable_map::TYPE_FLAGS);
+ flagAttr->symbols.push_back(Attribute::Symbol{
+ ResourceNameRef{ u"android", ResourceType::kId, u"apple" }, 1 });
+ flagAttr->symbols.push_back(Attribute::Symbol{
+ ResourceNameRef{ u"android", ResourceType::kId, u"banana" }, 2 });
+ addResource(ResourceName{ {}, ResourceType::kAttr, u"flags" }, std::move(flagAttr));
+ }
+
+ /*
+ * Convenience method for adding resources with the default configuration and some
+ * bogus source line.
+ */
+ bool addResource(const ResourceNameRef& name, std::unique_ptr<Value> value) {
+ return mTable->addResource(name, {}, SourceLine{ "test.xml", 21 }, std::move(value));
+ }
+
+ std::shared_ptr<ResourceTable> mTable;
+ std::shared_ptr<Linker> mLinker;
+};
+
+TEST_F(LinkerTest, DoNotInterpretEscapedStringAsReference) {
+ ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kString, u"foo" },
+ util::make_unique<String>(mTable->getValueStringPool().makeRef(u"?123"))));
+
+ ASSERT_TRUE(mLinker->linkAndValidate());
+ EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
+}
+
+TEST_F(LinkerTest, EscapeAndConvertRawString) {
+ std::unique_ptr<Style> style = util::make_unique<Style>();
+ style->entries.push_back(Style::Entry{
+ ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" },
+ util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u" 123"))
+ });
+ const Style* result = style.get();
+ ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
+ std::move(style)));
+
+ ASSERT_TRUE(mLinker->linkAndValidate());
+ EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
+
+ EXPECT_NE(nullptr, dynamic_cast<BinaryPrimitive*>(result->entries.front().value.get()));
+}
+
+TEST_F(LinkerTest, FailToConvertRawString) {
+ std::unique_ptr<Style> style = util::make_unique<Style>();
+ style->entries.push_back(Style::Entry{
+ ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" },
+ util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"yo what is up?"))
+ });
+ ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
+ std::move(style)));
+
+ ASSERT_FALSE(mLinker->linkAndValidate());
+}
+
+TEST_F(LinkerTest, ConvertRawStringToString) {
+ std::unique_ptr<Style> style = util::make_unique<Style>();
+ style->entries.push_back(Style::Entry{
+ ResourceNameRef{ u"android", ResourceType::kAttr, u"string" },
+ util::make_unique<RawString>(
+ mTable->getValueStringPool().makeRef(u" \"this is \\u00fa\"."))
+ });
+ const Style* result = style.get();
+ ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
+ std::move(style)));
+
+ ASSERT_TRUE(mLinker->linkAndValidate());
+ EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
+
+ const String* str = dynamic_cast<const String*>(result->entries.front().value.get());
+ ASSERT_NE(nullptr, str);
+ EXPECT_EQ(*str->value, u"this is \u00fa.");
+}
+
+TEST_F(LinkerTest, ConvertRawStringToFlags) {
+ std::unique_ptr<Style> style = util::make_unique<Style>();
+ style->entries.push_back(Style::Entry{
+ ResourceNameRef{ u"android", ResourceType::kAttr, u"flags" },
+ util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"banana | apple"))
+ });
+ const Style* result = style.get();
+ ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
+ std::move(style)));
+
+ ASSERT_TRUE(mLinker->linkAndValidate());
+ EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
+
+ const BinaryPrimitive* bin = dynamic_cast<const BinaryPrimitive*>(
+ result->entries.front().value.get());
+ ASSERT_NE(nullptr, bin);
+ EXPECT_EQ(bin->value.data, 1u | 2u);
+}
+
+TEST_F(LinkerTest, AllowReferenceWithOnlyResourceIdPointingToDifferentPackage) {
+ ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kInteger, u"foo" },
+ util::make_unique<Reference>(ResourceId{ 0x02, 0x01, 0x01 })));
+
+ ASSERT_TRUE(mLinker->linkAndValidate());
+ EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Locale.cpp b/tools/aapt2/Locale.cpp
new file mode 100644
index 0000000..eed0ea7
--- /dev/null
+++ b/tools/aapt2/Locale.cpp
@@ -0,0 +1,274 @@
+/*
+ * 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 "Locale.h"
+#include "Util.h"
+
+#include <algorithm>
+#include <ctype.h>
+#include <string>
+#include <vector>
+
+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::setRegion(const char* regionChars) {
+ size_t i = 0;
+ while ((*regionChars) != '\0') {
+ region[i++] = ::toupper(*regionChars);
+ regionChars++;
+ }
+}
+
+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::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);
+}
+
+static inline bool isNumber(const std::string& str) {
+ return std::all_of(std::begin(str), std::end(str), ::isdigit);
+}
+
+bool LocaleValue::initFromFilterString(const std::string& str) {
+ // 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() >= 5 && 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() >= 5 && 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() >= 5 && part4.length() <= 8) {
+ setVariant(part4.c_str());
+ } else {
+ valid = false;
+ }
+
+ if (!valid || numTags > 4) {
+ return false;
+ }
+
+ return true;
+}
+
+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:
+ 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;
+ }
+ }
+ }
+ }
+
+ return static_cast<ssize_t>(iter - startIter);
+}
+
+
+std::string LocaleValue::toDirName() const {
+ std::string dirName;
+ if (language[0]) {
+ dirName += language;
+ } else {
+ return dirName;
+ }
+
+ if (script[0]) {
+ dirName += "-s";
+ dirName += script;
+ }
+
+ if (region[0]) {
+ dirName += "-r";
+ dirName += region;
+ }
+
+ if (variant[0]) {
+ dirName += "-v";
+ dirName += variant;
+ }
+
+ return dirName;
+}
+
+void LocaleValue::initFromResTable(const ResTable_config& config) {
+ config.unpackLanguage(language);
+ config.unpackRegion(region);
+ if (config.localeScript[0]) {
+ memcpy(script, config.localeScript, sizeof(config.localeScript));
+ }
+
+ if (config.localeVariant[0]) {
+ memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
+ }
+}
+
+void LocaleValue::writeTo(ResTable_config* out) const {
+ out->packLanguage(language);
+ out->packRegion(region);
+
+ if (script[0]) {
+ memcpy(out->localeScript, script, sizeof(out->localeScript));
+ }
+
+ if (variant[0]) {
+ memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
+ }
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Locale.h b/tools/aapt2/Locale.h
new file mode 100644
index 0000000..ceec764
--- /dev/null
+++ b/tools/aapt2/Locale.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_LOCALE_VALUE_H
+#define AAPT_LOCALE_VALUE_H
+
+#include <androidfw/ResourceTypes.h>
+#include <string>
+#include <vector>
+
+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 std::string& 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);
+};
+
+//
+// Implementation
+//
+
+LocaleValue::LocaleValue() {
+ memset(this, 0, sizeof(LocaleValue));
+}
+
+int LocaleValue::compare(const LocaleValue& other) const {
+ return memcmp(this, &other, sizeof(LocaleValue));
+}
+
+bool LocaleValue::operator<(const LocaleValue& o) const {
+ return compare(o) < 0;
+}
+
+bool LocaleValue::operator<=(const LocaleValue& o) const {
+ return compare(o) <= 0;
+}
+
+bool LocaleValue::operator==(const LocaleValue& o) const {
+ return compare(o) == 0;
+}
+
+bool LocaleValue::operator!=(const LocaleValue& o) const {
+ return compare(o) != 0;
+}
+
+bool LocaleValue::operator>=(const LocaleValue& o) const {
+ return compare(o) >= 0;
+}
+
+bool LocaleValue::operator>(const LocaleValue& o) const {
+ return compare(o) > 0;
+}
+
+} // namespace aapt
+
+#endif // AAPT_LOCALE_VALUE_H
diff --git a/tools/aapt2/Locale_test.cpp b/tools/aapt2/Locale_test.cpp
new file mode 100644
index 0000000..4e154d6
--- /dev/null
+++ b/tools/aapt2/Locale_test.cpp
@@ -0,0 +1,82 @@
+/*
+ * 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 "Locale.h"
+#include "Util.h"
+
+#include <gtest/gtest.h>
+#include <string>
+
+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 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();
+}
+
+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(TestLanguageRegion("fr-rCA", "fr", "CA"));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Logger.cpp b/tools/aapt2/Logger.cpp
new file mode 100644
index 0000000..3847185
--- /dev/null
+++ b/tools/aapt2/Logger.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "Logger.h"
+#include "Source.h"
+
+#include <memory>
+#include <iostream>
+
+namespace aapt {
+
+Log::Log(std::ostream& _out, std::ostream& _err) : out(_out), err(_err) {
+}
+
+std::shared_ptr<Log> Logger::sLog(std::make_shared<Log>(std::cerr, std::cerr));
+
+void Logger::setLog(const std::shared_ptr<Log>& log) {
+ sLog = log;
+}
+
+std::ostream& Logger::error() {
+ return sLog->err << "error: ";
+}
+
+std::ostream& Logger::error(const Source& source) {
+ return sLog->err << source << ": error: ";
+}
+
+std::ostream& Logger::error(const SourceLine& source) {
+ return sLog->err << source << ": error: ";
+}
+
+std::ostream& Logger::warn() {
+ return sLog->err << "warning: ";
+}
+
+std::ostream& Logger::warn(const Source& source) {
+ return sLog->err << source << ": warning: ";
+}
+
+std::ostream& Logger::warn(const SourceLine& source) {
+ return sLog->err << source << ": warning: ";
+}
+
+std::ostream& Logger::note() {
+ return sLog->out << "note: ";
+}
+
+std::ostream& Logger::note(const Source& source) {
+ return sLog->err << source << ": note: ";
+}
+
+std::ostream& Logger::note(const SourceLine& source) {
+ return sLog->err << source << ": note: ";
+}
+
+SourceLogger::SourceLogger(const Source& source)
+: mSource(source) {
+}
+
+std::ostream& SourceLogger::error() {
+ return Logger::error(mSource);
+}
+
+std::ostream& SourceLogger::error(size_t line) {
+ return Logger::error(SourceLine{ mSource.path, line });
+}
+
+std::ostream& SourceLogger::warn() {
+ return Logger::warn(mSource);
+}
+
+std::ostream& SourceLogger::warn(size_t line) {
+ return Logger::warn(SourceLine{ mSource.path, line });
+}
+
+std::ostream& SourceLogger::note() {
+ return Logger::note(mSource);
+}
+
+std::ostream& SourceLogger::note(size_t line) {
+ return Logger::note(SourceLine{ mSource.path, line });
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Logger.h b/tools/aapt2/Logger.h
new file mode 100644
index 0000000..1d437eb
--- /dev/null
+++ b/tools/aapt2/Logger.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_LOGGER_H
+#define AAPT_LOGGER_H
+
+#include "Source.h"
+
+#include <memory>
+#include <ostream>
+#include <string>
+#include <utils/String8.h>
+
+namespace aapt {
+
+struct Log {
+ Log(std::ostream& out, std::ostream& err);
+ Log(const Log& rhs) = delete;
+
+ std::ostream& out;
+ std::ostream& err;
+};
+
+class Logger {
+public:
+ static void setLog(const std::shared_ptr<Log>& log);
+
+ static std::ostream& error();
+ static std::ostream& error(const Source& source);
+ static std::ostream& error(const SourceLine& sourceLine);
+
+ static std::ostream& warn();
+ static std::ostream& warn(const Source& source);
+ static std::ostream& warn(const SourceLine& sourceLine);
+
+ static std::ostream& note();
+ static std::ostream& note(const Source& source);
+ static std::ostream& note(const SourceLine& sourceLine);
+
+private:
+ static std::shared_ptr<Log> sLog;
+};
+
+class SourceLogger {
+public:
+ SourceLogger(const Source& source);
+
+ std::ostream& error();
+ std::ostream& error(size_t line);
+
+ std::ostream& warn();
+ std::ostream& warn(size_t line);
+
+ std::ostream& note();
+ std::ostream& note(size_t line);
+
+private:
+ Source mSource;
+};
+
+inline ::std::ostream& operator<<(::std::ostream& out, const std::u16string& str) {
+ android::String8 utf8(str.data(), str.size());
+ return out.write(utf8.string(), utf8.size());
+}
+
+} // namespace aapt
+
+#endif // AAPT_LOGGER_H
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
new file mode 100644
index 0000000..41c229d
--- /dev/null
+++ b/tools/aapt2/Main.cpp
@@ -0,0 +1,1277 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "AppInfo.h"
+#include "BigBuffer.h"
+#include "BinaryResourceParser.h"
+#include "BindingXmlPullParser.h"
+#include "Debug.h"
+#include "Files.h"
+#include "Flag.h"
+#include "JavaClassGenerator.h"
+#include "Linker.h"
+#include "ManifestMerger.h"
+#include "ManifestParser.h"
+#include "ManifestValidator.h"
+#include "NameMangler.h"
+#include "Png.h"
+#include "ProguardRules.h"
+#include "ResourceParser.h"
+#include "ResourceTable.h"
+#include "ResourceTableResolver.h"
+#include "ResourceValues.h"
+#include "SdkConstants.h"
+#include "SourceXmlPullParser.h"
+#include "StringPiece.h"
+#include "TableFlattener.h"
+#include "Util.h"
+#include "XmlFlattener.h"
+#include "ZipFile.h"
+
+#include <algorithm>
+#include <androidfw/AssetManager.h>
+#include <cstdlib>
+#include <dirent.h>
+#include <errno.h>
+#include <fstream>
+#include <iostream>
+#include <sstream>
+#include <sys/stat.h>
+#include <unordered_set>
+#include <utils/Errors.h>
+
+constexpr const char* kAaptVersionStr = "2.0-alpha";
+
+using namespace aapt;
+
+/**
+ * Used with smart pointers to free malloc'ed memory.
+ */
+struct DeleteMalloc {
+ void operator()(void* ptr) {
+ free(ptr);
+ }
+};
+
+struct StaticLibraryData {
+ Source source;
+ std::unique_ptr<ZipFile> apk;
+};
+
+/**
+ * Collect files from 'root', filtering out any files that do not
+ * match the FileFilter 'filter'.
+ */
+bool walkTree(const Source& root, const FileFilter& filter,
+ std::vector<Source>* outEntries) {
+ bool error = false;
+
+ for (const std::string& dirName : listFiles(root.path)) {
+ std::string dir = root.path;
+ appendPath(&dir, dirName);
+
+ FileType ft = getFileType(dir);
+ if (!filter(dirName, ft)) {
+ continue;
+ }
+
+ if (ft != FileType::kDirectory) {
+ continue;
+ }
+
+ for (const std::string& fileName : listFiles(dir)) {
+ std::string file(dir);
+ appendPath(&file, fileName);
+
+ FileType ft = getFileType(file);
+ if (!filter(fileName, ft)) {
+ continue;
+ }
+
+ if (ft != FileType::kRegular) {
+ Logger::error(Source{ file }) << "not a regular file." << std::endl;
+ error = true;
+ continue;
+ }
+ outEntries->push_back(Source{ file });
+ }
+ }
+ return !error;
+}
+
+void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) {
+ for (auto& type : *table) {
+ if (type->type != ResourceType::kStyle) {
+ continue;
+ }
+
+ for (auto& entry : type->entries) {
+ // Add the versioned styles we want to create
+ // here. They are added to the table after
+ // iterating over the original set of styles.
+ //
+ // A stack is used since auto-generated styles
+ // from later versions should override
+ // auto-generated styles from earlier versions.
+ // Iterating over the styles is done in order,
+ // so we will always visit sdkVersions from smallest
+ // to largest.
+ std::stack<ResourceConfigValue> addStack;
+
+ for (ResourceConfigValue& configValue : entry->values) {
+ visitFunc<Style>(*configValue.value, [&](Style& style) {
+ // Collect which entries we've stripped and the smallest
+ // SDK level which was stripped.
+ size_t minSdkStripped = std::numeric_limits<size_t>::max();
+ std::vector<Style::Entry> stripped;
+
+ // Iterate over the style's entries and erase/record the
+ // attributes whose SDK level exceeds the config's sdkVersion.
+ auto iter = style.entries.begin();
+ while (iter != style.entries.end()) {
+ if (iter->key.name.package == u"android") {
+ size_t sdkLevel = findAttributeSdkLevel(iter->key.name);
+ if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) {
+ // Record that we are about to strip this.
+ stripped.emplace_back(std::move(*iter));
+ minSdkStripped = std::min(minSdkStripped, sdkLevel);
+
+ // Erase this from this style.
+ iter = style.entries.erase(iter);
+ continue;
+ }
+ }
+ ++iter;
+ }
+
+ if (!stripped.empty()) {
+ // We have stripped attributes, so let's create a new style to hold them.
+ ConfigDescription versionConfig(configValue.config);
+ versionConfig.sdkVersion = minSdkStripped;
+
+ ResourceConfigValue value = {
+ versionConfig,
+ configValue.source,
+ {},
+
+ // Create a copy of the original style.
+ std::unique_ptr<Value>(configValue.value->clone(
+ &table->getValueStringPool()))
+ };
+
+ Style& newStyle = static_cast<Style&>(*value.value);
+
+ // Move the recorded stripped attributes into this new style.
+ std::move(stripped.begin(), stripped.end(),
+ std::back_inserter(newStyle.entries));
+
+ // We will add this style to the table later. If we do it now, we will
+ // mess up iteration.
+ addStack.push(std::move(value));
+ }
+ });
+ }
+
+ auto comparator =
+ [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
+ return lhs.config < rhs;
+ };
+
+ while (!addStack.empty()) {
+ ResourceConfigValue& value = addStack.top();
+ auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
+ value.config, comparator);
+ if (iter == entry->values.end() || iter->config != value.config) {
+ entry->values.insert(iter, std::move(value));
+ }
+ addStack.pop();
+ }
+ }
+ }
+}
+
+struct CompileItem {
+ ResourceName name;
+ ConfigDescription config;
+ Source source;
+ std::string extension;
+};
+
+struct LinkItem {
+ ResourceName name;
+ ConfigDescription config;
+ Source source;
+ std::string originalPath;
+ ZipFile* apk;
+ std::u16string originalPackage;
+};
+
+template <typename TChar>
+static BasicStringPiece<TChar> getExtension(const BasicStringPiece<TChar>& str) {
+ auto iter = std::find(str.begin(), str.end(), static_cast<TChar>('.'));
+ if (iter == str.end()) {
+ return BasicStringPiece<TChar>();
+ }
+ size_t offset = (iter - str.begin()) + 1;
+ return str.substr(offset, str.size() - offset);
+}
+
+std::string buildFileReference(const ResourceNameRef& name, const ConfigDescription& config,
+ const StringPiece& extension) {
+ std::stringstream path;
+ path << "res/" << name.type;
+ if (config != ConfigDescription{}) {
+ path << "-" << config;
+ }
+ path << "/" << util::utf16ToUtf8(name.entry);
+ if (!extension.empty()) {
+ path << "." << extension;
+ }
+ return path.str();
+}
+
+std::string buildFileReference(const CompileItem& item) {
+ return buildFileReference(item.name, item.config, item.extension);
+}
+
+std::string buildFileReference(const LinkItem& item) {
+ return buildFileReference(item.name, item.config, getExtension<char>(item.originalPath));
+}
+
+template <typename T>
+bool addFileReference(const std::shared_ptr<ResourceTable>& table, const T& item) {
+ StringPool& pool = table->getValueStringPool();
+ StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)),
+ StringPool::Context{ 0, item.config });
+ return table->addResource(item.name, item.config, item.source.line(0),
+ util::make_unique<FileReference>(ref));
+}
+
+struct AaptOptions {
+ enum class Phase {
+ Link,
+ Compile,
+ Dump,
+ DumpStyleGraph,
+ };
+
+ enum class PackageType {
+ StandardApp,
+ StaticLibrary,
+ };
+
+ // The phase to process.
+ Phase phase;
+
+ // The type of package to produce.
+ PackageType packageType = PackageType::StandardApp;
+
+ // Details about the app.
+ AppInfo appInfo;
+
+ // The location of the manifest file.
+ Source manifest;
+
+ // The APK files to link.
+ std::vector<Source> input;
+
+ // The libraries these files may reference.
+ std::vector<Source> libraries;
+
+ // Output path. This can be a directory or file
+ // depending on the phase.
+ Source output;
+
+ // Directory in which to write binding xml files.
+ Source bindingOutput;
+
+ // Directory to in which to generate R.java.
+ Maybe<Source> generateJavaClass;
+
+ // File in which to produce proguard rules.
+ Maybe<Source> generateProguardRules;
+
+ // Whether to output verbose details about
+ // compilation.
+ bool verbose = false;
+
+ // Whether or not to auto-version styles or layouts
+ // referencing attributes defined in a newer SDK
+ // level than the style or layout is defined for.
+ bool versionStylesAndLayouts = true;
+
+ // The target style that will have it's style hierarchy dumped
+ // when the phase is DumpStyleGraph.
+ ResourceName dumpStyleTarget;
+};
+
+struct IdCollector : public xml::Visitor {
+ IdCollector(const Source& source, const std::shared_ptr<ResourceTable>& table) :
+ mSource(source), mTable(table) {
+ }
+
+ virtual void visit(xml::Text* node) override {}
+
+ virtual void visit(xml::Namespace* node) override {
+ for (const auto& child : node->children) {
+ child->accept(this);
+ }
+ }
+
+ virtual void visit(xml::Element* node) override {
+ for (const xml::Attribute& attr : node->attributes) {
+ bool create = false;
+ bool priv = false;
+ ResourceNameRef nameRef;
+ if (ResourceParser::tryParseReference(attr.value, &nameRef, &create, &priv)) {
+ if (create) {
+ mTable->addResource(nameRef, {}, mSource.line(node->lineNumber),
+ util::make_unique<Id>());
+ }
+ }
+ }
+
+ for (const auto& child : node->children) {
+ child->accept(this);
+ }
+ }
+
+private:
+ Source mSource;
+ std::shared_ptr<ResourceTable> mTable;
+};
+
+bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
+ const CompileItem& item, ZipFile* outApk) {
+ std::ifstream in(item.source.path, std::ifstream::binary);
+ if (!in) {
+ Logger::error(item.source) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ SourceLogger logger(item.source);
+ std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
+ if (!root) {
+ return false;
+ }
+
+ // Collect any resource ID's declared here.
+ IdCollector idCollector(item.source, table);
+ root->accept(&idCollector);
+
+ BigBuffer outBuffer(1024);
+ if (!xml::flatten(root.get(), options.appInfo.package, &outBuffer)) {
+ logger.error() << "failed to encode XML." << std::endl;
+ return false;
+ }
+
+ // Write the resulting compiled XML file to the output APK.
+ if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
+ nullptr) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to write compiled '" << item.source
+ << "' to apk." << std::endl;
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Determines if a layout should be auto generated based on SDK level. We do not
+ * generate a layout if there is already a layout defined whose SDK version is greater than
+ * the one we want to generate.
+ */
+bool shouldGenerateVersionedResource(const std::shared_ptr<const ResourceTable>& table,
+ const ResourceName& name, const ConfigDescription& config,
+ int sdkVersionToGenerate) {
+ assert(sdkVersionToGenerate > config.sdkVersion);
+ const ResourceTableType* type;
+ const ResourceEntry* entry;
+ std::tie(type, entry) = table->findResource(name);
+ assert(type && entry);
+
+ auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), config,
+ [](const ResourceConfigValue& lhs, const ConfigDescription& config) -> bool {
+ return lhs.config < config;
+ });
+
+ assert(iter != entry->values.end());
+ ++iter;
+
+ if (iter == entry->values.end()) {
+ return true;
+ }
+
+ ConfigDescription newConfig = config;
+ newConfig.sdkVersion = sdkVersionToGenerate;
+ return newConfig < iter->config;
+}
+
+bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
+ const std::shared_ptr<IResolver>& resolver, const LinkItem& item,
+ const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue,
+ proguard::KeepSet* keepSet) {
+ SourceLogger logger(item.source);
+ std::unique_ptr<xml::Node> root = xml::inflate(data, dataLen, &logger);
+ if (!root) {
+ return false;
+ }
+
+ xml::FlattenOptions xmlOptions;
+ if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
+ xmlOptions.keepRawValues = true;
+ }
+
+ if (options.versionStylesAndLayouts) {
+ // We strip attributes that do not belong in this version of the resource.
+ // Non-version qualified resources have an implicit version 1 requirement.
+ xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
+ }
+
+ if (options.generateProguardRules) {
+ proguard::collectProguardRules(item.name.type, item.source, root.get(), keepSet);
+ }
+
+ BigBuffer outBuffer(1024);
+ Maybe<size_t> minStrippedSdk = xml::flattenAndLink(item.source, root.get(),
+ item.originalPackage, resolver,
+ xmlOptions, &outBuffer);
+ if (!minStrippedSdk) {
+ logger.error() << "failed to encode XML." << std::endl;
+ return false;
+ }
+
+ if (minStrippedSdk.value() > 0) {
+ // Something was stripped, so let's generate a new file
+ // with the version of the smallest SDK version stripped.
+ // We can only generate a versioned layout if there doesn't exist a layout
+ // with sdk version greater than the current one but less than the one we
+ // want to generate.
+ if (shouldGenerateVersionedResource(table, item.name, item.config,
+ minStrippedSdk.value())) {
+ LinkItem newWork = item;
+ newWork.config.sdkVersion = minStrippedSdk.value();
+ outQueue->push(newWork);
+
+ if (!addFileReference(table, newWork)) {
+ Logger::error(options.output) << "failed to add auto-versioned resource '"
+ << newWork.name << "'." << std::endl;
+ return false;
+ }
+ }
+ }
+
+ if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated,
+ nullptr) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to write linked file '"
+ << buildFileReference(item) << "' to apk." << std::endl;
+ return false;
+ }
+ return true;
+}
+
+bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
+ std::ifstream in(item.source.path, std::ifstream::binary);
+ if (!in) {
+ Logger::error(item.source) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ BigBuffer outBuffer(4096);
+ std::string err;
+ Png png;
+ if (!png.process(item.source, in, &outBuffer, {}, &err)) {
+ Logger::error(item.source) << err << std::endl;
+ return false;
+ }
+
+ if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
+ nullptr) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to write compiled '" << item.source
+ << "' to apk." << std::endl;
+ return false;
+ }
+ return true;
+}
+
+bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
+ if (outApk->add(item.source.path.data(), buildFileReference(item).data(),
+ ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk."
+ << std::endl;
+ return false;
+ }
+ return true;
+}
+
+bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
+ const std::map<std::shared_ptr<ResourceTable>, StaticLibraryData>& libApks,
+ const android::ResTable& table, ZipFile* outApk, proguard::KeepSet* keepSet) {
+ if (options.verbose) {
+ Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
+ }
+
+ std::ifstream in(options.manifest.path, std::ifstream::binary);
+ if (!in) {
+ Logger::error(options.manifest) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ SourceLogger logger(options.manifest);
+ std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
+ if (!root) {
+ return false;
+ }
+
+ ManifestMerger merger({});
+ if (!merger.setAppManifest(options.manifest, options.appInfo.package, std::move(root))) {
+ return false;
+ }
+
+ for (const auto& entry : libApks) {
+ ZipFile* libApk = entry.second.apk.get();
+ const std::u16string& libPackage = entry.first->getPackage();
+ const Source& libSource = entry.second.source;
+
+ ZipEntry* zipEntry = libApk->getEntryByName("AndroidManifest.xml");
+ if (!zipEntry) {
+ continue;
+ }
+
+ std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
+ libApk->uncompress(zipEntry));
+ assert(uncompressedData);
+
+ SourceLogger logger(libSource);
+ std::unique_ptr<xml::Node> libRoot = xml::inflate(uncompressedData.get(),
+ zipEntry->getUncompressedLen(), &logger);
+ if (!libRoot) {
+ return false;
+ }
+
+ if (!merger.mergeLibraryManifest(libSource, libPackage, std::move(libRoot))) {
+ return false;
+ }
+ }
+
+ if (options.generateProguardRules) {
+ proguard::collectProguardRulesForManifest(options.manifest, merger.getMergedXml(),
+ keepSet);
+ }
+
+ BigBuffer outBuffer(1024);
+ if (!xml::flattenAndLink(options.manifest, merger.getMergedXml(), options.appInfo.package,
+ resolver, {}, &outBuffer)) {
+ return false;
+ }
+
+ std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
+
+ android::ResXMLTree tree;
+ if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
+ return false;
+ }
+
+ ManifestValidator validator(table);
+ if (!validator.validate(options.manifest, &tree)) {
+ return false;
+ }
+
+ if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml",
+ ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk."
+ << std::endl;
+ return false;
+ }
+ return true;
+}
+
+static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
+ const ConfigDescription& config) {
+ std::ifstream in(source.path, std::ifstream::binary);
+ if (!in) {
+ Logger::error(source) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
+ ResourceParser parser(table, source, config, xmlParser);
+ return parser.parse();
+}
+
+struct ResourcePathData {
+ std::u16string resourceDir;
+ std::u16string name;
+ std::string extension;
+ ConfigDescription config;
+};
+
+/**
+ * Resource file paths are expected to look like:
+ * [--/res/]type[-config]/name
+ */
+static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
+ // TODO(adamlesinski): Use Windows path separator on windows.
+ std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
+ if (parts.size() < 2) {
+ Logger::error(source) << "bad resource path." << std::endl;
+ return {};
+ }
+
+ std::string& dir = parts[parts.size() - 2];
+ StringPiece dirStr = dir;
+
+ ConfigDescription config;
+ size_t dashPos = dir.find('-');
+ if (dashPos != std::string::npos) {
+ StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
+ if (!ConfigDescription::parse(configStr, &config)) {
+ Logger::error(source)
+ << "invalid configuration '"
+ << configStr
+ << "'."
+ << std::endl;
+ return {};
+ }
+ dirStr = dirStr.substr(0, dashPos);
+ }
+
+ std::string& filename = parts[parts.size() - 1];
+ StringPiece name = filename;
+ StringPiece extension;
+ size_t dotPos = filename.find('.');
+ if (dotPos != std::string::npos) {
+ extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
+ name = name.substr(0, dotPos);
+ }
+
+ return ResourcePathData{
+ util::utf8ToUtf16(dirStr),
+ util::utf8ToUtf16(name),
+ extension.toString(),
+ config
+ };
+}
+
+bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
+ const TableFlattener::Options& flattenerOptions, ZipFile* outApk) {
+ if (table->begin() != table->end()) {
+ BigBuffer buffer(1024);
+ TableFlattener flattener(flattenerOptions);
+ if (!flattener.flatten(&buffer, *table)) {
+ Logger::error() << "failed to flatten resource table." << std::endl;
+ return false;
+ }
+
+ if (options.verbose) {
+ Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
+ << std::endl;
+ }
+
+ if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) !=
+ android::NO_ERROR) {
+ Logger::note(options.output) << "failed to store resource table." << std::endl;
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * For each FileReference in the table, adds a LinkItem to the link queue for processing.
+ */
+static void addApkFilesToLinkQueue(const std::u16string& package, const Source& source,
+ const std::shared_ptr<ResourceTable>& table,
+ const std::unique_ptr<ZipFile>& apk,
+ std::queue<LinkItem>* outLinkQueue) {
+ bool mangle = package != table->getPackage();
+ for (auto& type : *table) {
+ for (auto& entry : type->entries) {
+ ResourceName name = { package, type->type, entry->name };
+ if (mangle) {
+ NameMangler::mangle(table->getPackage(), &name.entry);
+ }
+
+ for (auto& value : entry->values) {
+ visitFunc<FileReference>(*value.value, [&](FileReference& ref) {
+ std::string pathUtf8 = util::utf16ToUtf8(*ref.path);
+ Source newSource = source;
+ newSource.path += "/";
+ newSource.path += pathUtf8;
+ outLinkQueue->push(LinkItem{
+ name, value.config, newSource, pathUtf8, apk.get(),
+ table->getPackage() });
+ // Now rewrite the file path.
+ if (mangle) {
+ ref.path = table->getValueStringPool().makeRef(util::utf8ToUtf16(
+ buildFileReference(name, value.config,
+ getExtension<char>(pathUtf8))));
+ }
+ });
+ }
+ }
+ }
+}
+
+static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
+ ZipFile::kOpenReadWrite;
+
+bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
+ const std::shared_ptr<IResolver>& resolver) {
+ std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles;
+ std::unordered_set<std::u16string> linkedPackages;
+
+ // Populate the linkedPackages with our own.
+ linkedPackages.insert(options.appInfo.package);
+
+ // Load all APK files.
+ for (const Source& source : options.input) {
+ std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
+ if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
+ Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
+ return false;
+ }
+
+ std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
+
+ ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
+ if (!entry) {
+ Logger::error(source) << "missing 'resources.arsc'." << std::endl;
+ return false;
+ }
+
+ std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
+ zipFile->uncompress(entry));
+ assert(uncompressedData);
+
+ BinaryResourceParser parser(table, resolver, source, uncompressedData.get(),
+ entry->getUncompressedLen());
+ if (!parser.parse()) {
+ return false;
+ }
+
+ // Keep track of where this table came from.
+ apkFiles[table] = StaticLibraryData{ source, std::move(zipFile) };
+
+ // Add the package to the set of linked packages.
+ linkedPackages.insert(table->getPackage());
+ }
+
+ std::queue<LinkItem> linkQueue;
+ for (auto& p : apkFiles) {
+ const std::shared_ptr<ResourceTable>& inTable = p.first;
+
+ // Collect all FileReferences and add them to the queue for processing.
+ addApkFilesToLinkQueue(options.appInfo.package, p.second.source, inTable, p.second.apk,
+ &linkQueue);
+
+ // Merge the tables.
+ if (!outTable->merge(std::move(*inTable))) {
+ return false;
+ }
+ }
+
+ // Version all styles referencing attributes outside of their specified SDK version.
+ if (options.versionStylesAndLayouts) {
+ versionStylesForCompat(outTable);
+ }
+
+ {
+ // Now that everything is merged, let's link it.
+ Linker::Options linkerOptions;
+ if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
+ linkerOptions.linkResourceIds = false;
+ }
+ Linker linker(outTable, resolver, linkerOptions);
+ if (!linker.linkAndValidate()) {
+ return false;
+ }
+
+ // Verify that all symbols exist.
+ const auto& unresolvedRefs = linker.getUnresolvedReferences();
+ if (!unresolvedRefs.empty()) {
+ for (const auto& entry : unresolvedRefs) {
+ for (const auto& source : entry.second) {
+ Logger::error(source) << "unresolved symbol '" << entry.first << "'."
+ << std::endl;
+ }
+ }
+ return false;
+ }
+ }
+
+ // Open the output APK file for writing.
+ ZipFile outApk;
+ if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
+ return false;
+ }
+
+ proguard::KeepSet keepSet;
+
+ android::ResTable binTable;
+ if (!compileManifest(options, resolver, apkFiles, binTable, &outApk, &keepSet)) {
+ return false;
+ }
+
+ for (; !linkQueue.empty(); linkQueue.pop()) {
+ const LinkItem& item = linkQueue.front();
+
+ assert(!item.originalPackage.empty());
+ ZipEntry* entry = item.apk->getEntryByName(item.originalPath.data());
+ if (!entry) {
+ Logger::error(item.source) << "failed to find '" << item.originalPath << "'."
+ << std::endl;
+ return false;
+ }
+
+ if (util::stringEndsWith<char>(item.originalPath, ".xml")) {
+ void* uncompressedData = item.apk->uncompress(entry);
+ assert(uncompressedData);
+
+ if (!linkXml(options, outTable, resolver, item, uncompressedData,
+ entry->getUncompressedLen(), &outApk, &linkQueue, &keepSet)) {
+ Logger::error(options.output) << "failed to link '" << item.originalPath << "'."
+ << std::endl;
+ return false;
+ }
+ } else {
+ if (outApk.add(item.apk, entry, buildFileReference(item).data(), 0, nullptr) !=
+ android::NO_ERROR) {
+ Logger::error(options.output) << "failed to copy '" << item.originalPath << "'."
+ << std::endl;
+ return false;
+ }
+ }
+ }
+
+ // Generate the Java class file.
+ if (options.generateJavaClass) {
+ JavaClassGenerator::Options javaOptions;
+ if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
+ javaOptions.useFinal = false;
+ }
+ JavaClassGenerator generator(outTable, javaOptions);
+
+ for (const std::u16string& package : linkedPackages) {
+ Source outPath = options.generateJavaClass.value();
+
+ // Build the output directory from the package name.
+ // Eg. com.android.app -> com/android/app
+ const std::string packageUtf8 = util::utf16ToUtf8(package);
+ for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
+ appendPath(&outPath.path, part);
+ }
+
+ if (!mkdirs(outPath.path)) {
+ Logger::error(outPath) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ appendPath(&outPath.path, "R.java");
+
+ if (options.verbose) {
+ Logger::note(outPath) << "writing Java symbols." << std::endl;
+ }
+
+ std::ofstream fout(outPath.path);
+ if (!fout) {
+ Logger::error(outPath) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ if (!generator.generate(package, fout)) {
+ Logger::error(outPath) << generator.getError() << "." << std::endl;
+ return false;
+ }
+ }
+ }
+
+ // Generate the Proguard rules file.
+ if (options.generateProguardRules) {
+ const Source& outPath = options.generateProguardRules.value();
+
+ if (options.verbose) {
+ Logger::note(outPath) << "writing proguard rules." << std::endl;
+ }
+
+ std::ofstream fout(outPath.path);
+ if (!fout) {
+ Logger::error(outPath) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ if (!proguard::writeKeepSet(&fout, keepSet)) {
+ Logger::error(outPath) << "failed to write proguard rules." << std::endl;
+ return false;
+ }
+ }
+
+ outTable->getValueStringPool().prune();
+ outTable->getValueStringPool().sort(
+ [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+ if (a.context.priority < b.context.priority) {
+ return true;
+ }
+
+ if (a.context.priority > b.context.priority) {
+ return false;
+ }
+ return a.value < b.value;
+ });
+
+
+ // Flatten the resource table.
+ TableFlattener::Options flattenerOptions;
+ if (options.packageType != AaptOptions::PackageType::StaticLibrary) {
+ flattenerOptions.useExtendedChunks = false;
+ }
+
+ if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
+ return false;
+ }
+
+ outApk.flush();
+ return true;
+}
+
+bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
+ const std::shared_ptr<IResolver>& resolver) {
+ std::queue<CompileItem> compileQueue;
+ bool error = false;
+
+ // Compile all the resource files passed in on the command line.
+ for (const Source& source : options.input) {
+ // Need to parse the resource type/config/filename.
+ Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
+ if (!maybePathData) {
+ return false;
+ }
+
+ const ResourcePathData& pathData = maybePathData.value();
+ if (pathData.resourceDir == u"values") {
+ // The file is in the values directory, which means its contents will
+ // go into the resource table.
+ if (options.verbose) {
+ Logger::note(source) << "compiling values." << std::endl;
+ }
+
+ error |= !compileValues(table, source, pathData.config);
+ } else {
+ // The file is in a directory like 'layout' or 'drawable'. Find out
+ // the type.
+ const ResourceType* type = parseResourceType(pathData.resourceDir);
+ if (!type) {
+ Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
+ << std::endl;
+ return false;
+ }
+
+ compileQueue.push(CompileItem{
+ ResourceName{ table->getPackage(), *type, pathData.name },
+ pathData.config,
+ source,
+ pathData.extension
+ });
+ }
+ }
+
+ if (error) {
+ return false;
+ }
+ // Open the output APK file for writing.
+ ZipFile outApk;
+ if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
+ return false;
+ }
+
+ // Compile each file.
+ for (; !compileQueue.empty(); compileQueue.pop()) {
+ const CompileItem& item = compileQueue.front();
+
+ // Add the file name to the resource table.
+ error |= !addFileReference(table, item);
+
+ if (item.extension == "xml") {
+ error |= !compileXml(options, table, item, &outApk);
+ } else if (item.extension == "png" || item.extension == "9.png") {
+ error |= !compilePng(options, item, &outApk);
+ } else {
+ error |= !copyFile(options, item, &outApk);
+ }
+ }
+
+ if (error) {
+ return false;
+ }
+
+ // Link and assign resource IDs.
+ Linker linker(table, resolver, {});
+ if (!linker.linkAndValidate()) {
+ return false;
+ }
+
+ // Flatten the resource table.
+ if (!writeResourceTable(options, table, {}, &outApk)) {
+ return false;
+ }
+
+ outApk.flush();
+ return true;
+}
+
+bool loadAppInfo(const Source& source, AppInfo* outInfo) {
+ std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
+ if (!ifs) {
+ Logger::error(source) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ ManifestParser parser;
+ std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
+ return parser.parse(source, pullParser, outInfo);
+}
+
+static void printCommandsAndDie() {
+ std::cerr << "The following commands are supported:" << std::endl << std::endl;
+ std::cerr << "compile compiles a subset of resources" << std::endl;
+ std::cerr << "link links together compiled resources and libraries" << std::endl;
+ std::cerr << "dump dumps resource contents to to standard out" << std::endl;
+ std::cerr << std::endl;
+ std::cerr << "run aapt2 with one of the commands and the -h flag for extra details."
+ << std::endl;
+ exit(1);
+}
+
+static AaptOptions prepareArgs(int argc, char** argv) {
+ if (argc < 2) {
+ std::cerr << "no command specified." << std::endl << std::endl;
+ printCommandsAndDie();
+ }
+
+ const StringPiece command(argv[1]);
+ argc -= 2;
+ argv += 2;
+
+ AaptOptions options;
+
+ if (command == "--version" || command == "version") {
+ std::cout << kAaptVersionStr << std::endl;
+ exit(0);
+ } else if (command == "link") {
+ options.phase = AaptOptions::Phase::Link;
+ } else if (command == "compile") {
+ options.phase = AaptOptions::Phase::Compile;
+ } else if (command == "dump") {
+ options.phase = AaptOptions::Phase::Dump;
+ } else if (command == "dump-style-graph") {
+ options.phase = AaptOptions::Phase::DumpStyleGraph;
+ } else {
+ std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
+ printCommandsAndDie();
+ }
+
+ bool isStaticLib = false;
+ if (options.phase == AaptOptions::Phase::Compile ||
+ options.phase == AaptOptions::Phase::Link) {
+ if (options.phase == AaptOptions::Phase::Compile) {
+ flag::requiredFlag("--package", "Android package name",
+ [&options](const StringPiece& arg) {
+ options.appInfo.package = util::utf8ToUtf16(arg);
+ });
+ } else if (options.phase == AaptOptions::Phase::Link) {
+ flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
+ [&options](const StringPiece& arg) {
+ options.manifest = Source{ arg.toString() };
+ });
+
+ flag::optionalFlag("-I", "add an Android APK to link against",
+ [&options](const StringPiece& arg) {
+ options.libraries.push_back(Source{ arg.toString() });
+ });
+
+ flag::optionalFlag("--java", "directory in which to generate R.java",
+ [&options](const StringPiece& arg) {
+ options.generateJavaClass = Source{ arg.toString() };
+ });
+
+ flag::optionalFlag("--proguard", "file in which to output proguard rules",
+ [&options](const StringPiece& arg) {
+ options.generateProguardRules = Source{ arg.toString() };
+ });
+
+ flag::optionalSwitch("--static-lib", "generate a static Android library", true,
+ &isStaticLib);
+
+ flag::optionalFlag("--binding", "Output directory for binding XML files",
+ [&options](const StringPiece& arg) {
+ options.bindingOutput = Source{ arg.toString() };
+ });
+ flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning",
+ false, &options.versionStylesAndLayouts);
+ }
+
+ // Common flags for all steps.
+ flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
+ options.output = Source{ arg.toString() };
+ });
+ } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
+ flag::requiredFlag("--style", "Name of the style to dump",
+ [&options](const StringPiece& arg, std::string* outError) -> bool {
+ Reference styleReference;
+ if (!ResourceParser::parseStyleParentReference(util::utf8ToUtf16(arg),
+ &styleReference, outError)) {
+ return false;
+ }
+ options.dumpStyleTarget = styleReference.name;
+ return true;
+ });
+ }
+
+ bool help = false;
+ flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose);
+ flag::optionalSwitch("-h", "displays this help menu", true, &help);
+
+ // Build the command string for output (eg. "aapt2 compile").
+ std::string fullCommand = "aapt2";
+ fullCommand += " ";
+ fullCommand += command.toString();
+
+ // Actually read the command line flags.
+ flag::parse(argc, argv, fullCommand);
+
+ if (help) {
+ flag::usageAndDie(fullCommand);
+ }
+
+ if (isStaticLib) {
+ options.packageType = AaptOptions::PackageType::StaticLibrary;
+ }
+
+ // Copy all the remaining arguments.
+ for (const std::string& arg : flag::getArgs()) {
+ options.input.push_back(Source{ arg });
+ }
+ return options;
+}
+
+static bool doDump(const AaptOptions& options) {
+ for (const Source& source : options.input) {
+ std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
+ if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
+ Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
+ return false;
+ }
+
+ std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
+ std::shared_ptr<ResourceTableResolver> resolver =
+ std::make_shared<ResourceTableResolver>(
+ table, std::vector<std::shared_ptr<const android::AssetManager>>());
+
+ ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
+ if (!entry) {
+ Logger::error(source) << "missing 'resources.arsc'." << std::endl;
+ return false;
+ }
+
+ std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
+ zipFile->uncompress(entry));
+ assert(uncompressedData);
+
+ BinaryResourceParser parser(table, resolver, source, uncompressedData.get(),
+ entry->getUncompressedLen());
+ if (!parser.parse()) {
+ return false;
+ }
+
+ if (options.phase == AaptOptions::Phase::Dump) {
+ Debug::printTable(table);
+ } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
+ Debug::printStyleGraph(table, options.dumpStyleTarget);
+ }
+ }
+ return true;
+}
+
+int main(int argc, char** argv) {
+ Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
+ AaptOptions options = prepareArgs(argc, argv);
+
+ if (options.phase == AaptOptions::Phase::Dump ||
+ options.phase == AaptOptions::Phase::DumpStyleGraph) {
+ if (!doDump(options)) {
+ return 1;
+ }
+ return 0;
+ }
+
+ // If we specified a manifest, go ahead and load the package name from the manifest.
+ if (!options.manifest.path.empty()) {
+ if (!loadAppInfo(options.manifest, &options.appInfo)) {
+ return false;
+ }
+ }
+
+ // Verify we have some common options set.
+ if (options.appInfo.package.empty()) {
+ Logger::error() << "no package name specified." << std::endl;
+ return false;
+ }
+
+ // Every phase needs a resource table.
+ std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
+ table->setPackage(options.appInfo.package);
+ if (options.appInfo.package == u"android") {
+ table->setPackageId(0x01);
+ } else {
+ table->setPackageId(0x7f);
+ }
+
+ // Load the included libraries.
+ std::vector<std::shared_ptr<const android::AssetManager>> sources;
+ for (const Source& source : options.libraries) {
+ std::shared_ptr<android::AssetManager> assetManager =
+ std::make_shared<android::AssetManager>();
+ int32_t cookie;
+ if (!assetManager->addAssetPath(android::String8(source.path.data()), &cookie)) {
+ Logger::error(source) << "failed to load library." << std::endl;
+ return false;
+ }
+
+ if (cookie == 0) {
+ Logger::error(source) << "failed to load library." << std::endl;
+ return false;
+ }
+ sources.push_back(assetManager);
+ }
+
+ // Make the resolver that will cache IDs for us.
+ std::shared_ptr<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>(
+ table, sources);
+
+ if (options.phase == AaptOptions::Phase::Compile) {
+ if (!compile(options, table, resolver)) {
+ Logger::error() << "aapt exiting with failures." << std::endl;
+ return 1;
+ }
+ } else if (options.phase == AaptOptions::Phase::Link) {
+ if (!link(options, table, resolver)) {
+ Logger::error() << "aapt exiting with failures." << std::endl;
+ return 1;
+ }
+ }
+ return 0;
+}
diff --git a/tools/aapt2/ManifestMerger.cpp b/tools/aapt2/ManifestMerger.cpp
new file mode 100644
index 0000000..71d3424
--- /dev/null
+++ b/tools/aapt2/ManifestMerger.cpp
@@ -0,0 +1,376 @@
+#include "ManifestMerger.h"
+#include "Maybe.h"
+#include "ResourceParser.h"
+#include "Source.h"
+#include "Util.h"
+#include "XmlPullParser.h"
+
+#include <iostream>
+#include <memory>
+#include <set>
+#include <string>
+
+namespace aapt {
+
+constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
+
+static xml::Element* findManifest(xml::Node* root) {
+ if (!root) {
+ return nullptr;
+ }
+
+ while (root->type == xml::NodeType::kNamespace) {
+ if (root->children.empty()) {
+ break;
+ }
+ root = root->children[0].get();
+ }
+
+ if (root && root->type == xml::NodeType::kElement) {
+ xml::Element* el = static_cast<xml::Element*>(root);
+ if (el->namespaceUri.empty() && el->name == u"manifest") {
+ return el;
+ }
+ }
+ return nullptr;
+}
+
+static xml::Element* findChildWithSameName(xml::Element* parent, xml::Element* src) {
+ xml::Attribute* attrKey = src->findAttribute(kSchemaAndroid, u"name");
+ if (!attrKey) {
+ return nullptr;
+ }
+ return parent->findChildWithAttribute(src->namespaceUri, src->name, attrKey);
+}
+
+static bool attrLess(const xml::Attribute& lhs, const xml::Attribute& rhs) {
+ return std::tie(lhs.namespaceUri, lhs.name, lhs.value)
+ < std::tie(rhs.namespaceUri, rhs.name, rhs.value);
+}
+
+static int compare(xml::Element* lhs, xml::Element* rhs) {
+ int diff = lhs->attributes.size() - rhs->attributes.size();
+ if (diff != 0) {
+ return diff;
+ }
+
+ std::set<xml::Attribute, decltype(&attrLess)> lhsAttrs(&attrLess);
+ lhsAttrs.insert(lhs->attributes.begin(), lhs->attributes.end());
+ for (auto& attr : rhs->attributes) {
+ if (lhsAttrs.erase(attr) == 0) {
+ // The rhs attribute is not in the left.
+ return -1;
+ }
+ }
+
+ if (!lhsAttrs.empty()) {
+ // The lhs has attributes not in the rhs.
+ return 1;
+ }
+ return 0;
+}
+
+ManifestMerger::ManifestMerger(const Options& options) :
+ mOptions(options), mAppLogger({}), mLogger({}) {
+}
+
+bool ManifestMerger::setAppManifest(const Source& source, const std::u16string& package,
+ std::unique_ptr<xml::Node> root) {
+
+ mAppLogger = SourceLogger{ source };
+ mRoot = std::move(root);
+ return true;
+}
+
+bool ManifestMerger::checkEqual(xml::Element* elA, xml::Element* elB) {
+ if (compare(elA, elB) != 0) {
+ mLogger.error(elB->lineNumber)
+ << "library tag '" << elB->name << "' conflicts with app tag."
+ << std::endl;
+ mAppLogger.note(elA->lineNumber)
+ << "app tag '" << elA->name << "' defined here."
+ << std::endl;
+ return false;
+ }
+
+ std::vector<xml::Element*> childrenA = elA->getChildElements();
+ std::vector<xml::Element*> childrenB = elB->getChildElements();
+
+ if (childrenA.size() != childrenB.size()) {
+ mLogger.error(elB->lineNumber)
+ << "library tag '" << elB->name << "' children conflict with app tag."
+ << std::endl;
+ mAppLogger.note(elA->lineNumber)
+ << "app tag '" << elA->name << "' defined here."
+ << std::endl;
+ return false;
+ }
+
+ auto cmp = [](xml::Element* lhs, xml::Element* rhs) -> bool {
+ return compare(lhs, rhs) < 0;
+ };
+
+ std::sort(childrenA.begin(), childrenA.end(), cmp);
+ std::sort(childrenB.begin(), childrenB.end(), cmp);
+
+ for (size_t i = 0; i < childrenA.size(); i++) {
+ if (!checkEqual(childrenA[i], childrenB[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool ManifestMerger::mergeNewOrEqual(xml::Element* parentA, xml::Element* elA, xml::Element* elB) {
+ if (!elA) {
+ parentA->addChild(elB->clone());
+ return true;
+ }
+ return checkEqual(elA, elB);
+}
+
+bool ManifestMerger::mergePreferRequired(xml::Element* parentA, xml::Element* elA,
+ xml::Element* elB) {
+ if (!elA) {
+ parentA->addChild(elB->clone());
+ return true;
+ }
+
+ xml::Attribute* reqA = elA->findAttribute(kSchemaAndroid, u"required");
+ xml::Attribute* reqB = elB->findAttribute(kSchemaAndroid, u"required");
+ bool requiredA = !reqA || (reqA->value != u"false" && reqA->value != u"FALSE");
+ bool requiredB = !reqB || (reqB->value != u"false" && reqB->value != u"FALSE");
+ if (!requiredA && requiredB) {
+ if (reqA) {
+ *reqA = xml::Attribute{ kSchemaAndroid, u"required", u"true" };
+ } else {
+ elA->attributes.push_back(xml::Attribute{ kSchemaAndroid, u"required", u"true" });
+ }
+ }
+ return true;
+}
+
+static int findIntegerValue(xml::Attribute* attr, int defaultValue) {
+ if (attr) {
+ std::unique_ptr<BinaryPrimitive> integer = ResourceParser::tryParseInt(attr->value);
+ if (integer) {
+ return integer->value.data;
+ }
+ }
+ return defaultValue;
+}
+
+bool ManifestMerger::mergeUsesSdk(xml::Element* elA, xml::Element* elB) {
+ bool error = false;
+ xml::Attribute* minAttrA = nullptr;
+ xml::Attribute* minAttrB = nullptr;
+ if (elA) {
+ minAttrA = elA->findAttribute(kSchemaAndroid, u"minSdkVersion");
+ }
+
+ if (elB) {
+ minAttrB = elB->findAttribute(kSchemaAndroid, u"minSdkVersion");
+ }
+
+ int minSdkA = findIntegerValue(minAttrA, 1);
+ int minSdkB = findIntegerValue(minAttrB, 1);
+
+ if (minSdkA < minSdkB) {
+ std::ostream* out;
+ if (minAttrA) {
+ out = &(mAppLogger.error(elA->lineNumber) << "app declares ");
+ } else if (elA) {
+ out = &(mAppLogger.error(elA->lineNumber) << "app has implied ");
+ } else {
+ out = &(mAppLogger.error() << "app has implied ");
+ }
+
+ *out << "minSdkVersion=" << minSdkA << " but library expects a higher SDK version."
+ << std::endl;
+
+ // elB is valid because minSdkB wouldn't be greater than minSdkA if it wasn't.
+ mLogger.note(elB->lineNumber)
+ << "library declares minSdkVersion=" << minSdkB << "."
+ << std::endl;
+ error = true;
+ }
+
+ xml::Attribute* targetAttrA = nullptr;
+ xml::Attribute* targetAttrB = nullptr;
+
+ if (elA) {
+ targetAttrA = elA->findAttribute(kSchemaAndroid, u"targetSdkVersion");
+ }
+
+ if (elB) {
+ targetAttrB = elB->findAttribute(kSchemaAndroid, u"targetSdkVersion");
+ }
+
+ int targetSdkA = findIntegerValue(targetAttrA, minSdkA);
+ int targetSdkB = findIntegerValue(targetAttrB, minSdkB);
+
+ if (targetSdkA < targetSdkB) {
+ std::ostream* out;
+ if (targetAttrA) {
+ out = &(mAppLogger.warn(elA->lineNumber) << "app declares ");
+ } else if (elA) {
+ out = &(mAppLogger.warn(elA->lineNumber) << "app has implied ");
+ } else {
+ out = &(mAppLogger.warn() << "app has implied ");
+ }
+
+ *out << "targetSdkVerion=" << targetSdkA << " but library expects target SDK "
+ << targetSdkB << "." << std::endl;
+
+ mLogger.note(elB->lineNumber)
+ << "library declares targetSdkVersion=" << targetSdkB << "."
+ << std::endl;
+ error = true;
+ }
+ return !error;
+}
+
+bool ManifestMerger::mergeApplication(xml::Element* applicationA, xml::Element* applicationB) {
+ if (!applicationA || !applicationB) {
+ return true;
+ }
+
+ bool error = false;
+
+ // First make sure that the names are identical.
+ xml::Attribute* nameA = applicationA->findAttribute(kSchemaAndroid, u"name");
+ xml::Attribute* nameB = applicationB->findAttribute(kSchemaAndroid, u"name");
+ if (nameB) {
+ if (!nameA) {
+ applicationA->attributes.push_back(*nameB);
+ } else if (nameA->value != nameB->value) {
+ mLogger.error(applicationB->lineNumber)
+ << "conflicting application name '"
+ << nameB->value
+ << "'." << std::endl;
+ mAppLogger.note(applicationA->lineNumber)
+ << "application defines application name '"
+ << nameA->value
+ << "'." << std::endl;
+ error = true;
+ }
+ }
+
+ // Now we descend into the activity/receiver/service/provider tags
+ for (xml::Element* elB : applicationB->getChildElements()) {
+ if (!elB->namespaceUri.empty()) {
+ continue;
+ }
+
+ if (elB->name == u"activity" || elB->name == u"activity-alias"
+ || elB->name == u"service" || elB->name == u"receiver"
+ || elB->name == u"provider" || elB->name == u"meta-data") {
+ xml::Element* elA = findChildWithSameName(applicationA, elB);
+ error |= !mergeNewOrEqual(applicationA, elA, elB);
+ } else if (elB->name == u"uses-library") {
+ xml::Element* elA = findChildWithSameName(applicationA, elB);
+ error |= !mergePreferRequired(applicationA, elA, elB);
+ }
+ }
+ return !error;
+}
+
+bool ManifestMerger::mergeLibraryManifest(const Source& source, const std::u16string& package,
+ std::unique_ptr<xml::Node> libRoot) {
+ mLogger = SourceLogger{ source };
+ xml::Element* manifestA = findManifest(mRoot.get());
+ xml::Element* manifestB = findManifest(libRoot.get());
+ if (!manifestA) {
+ mAppLogger.error() << "missing manifest tag." << std::endl;
+ return false;
+ }
+
+ if (!manifestB) {
+ mLogger.error() << "library missing manifest tag." << std::endl;
+ return false;
+ }
+
+ bool error = false;
+
+ // Do <application> first.
+ xml::Element* applicationA = manifestA->findChild({}, u"application");
+ xml::Element* applicationB = manifestB->findChild({}, u"application");
+ error |= !mergeApplication(applicationA, applicationB);
+
+ // Do <uses-sdk> next.
+ xml::Element* usesSdkA = manifestA->findChild({}, u"uses-sdk");
+ xml::Element* usesSdkB = manifestB->findChild({}, u"uses-sdk");
+ error |= !mergeUsesSdk(usesSdkA, usesSdkB);
+
+ for (xml::Element* elB : manifestB->getChildElements()) {
+ if (!elB->namespaceUri.empty()) {
+ continue;
+ }
+
+ if (elB->name == u"uses-permission" || elB->name == u"permission"
+ || elB->name == u"permission-group" || elB->name == u"permission-tree") {
+ xml::Element* elA = findChildWithSameName(manifestA, elB);
+ error |= !mergeNewOrEqual(manifestA, elA, elB);
+ } else if (elB->name == u"uses-feature") {
+ xml::Element* elA = findChildWithSameName(manifestA, elB);
+ error |= !mergePreferRequired(manifestA, elA, elB);
+ } else if (elB->name == u"uses-configuration" || elB->name == u"supports-screen"
+ || elB->name == u"compatible-screens" || elB->name == u"supports-gl-texture") {
+ xml::Element* elA = findChildWithSameName(manifestA, elB);
+ error |= !checkEqual(elA, elB);
+ }
+ }
+ return !error;
+}
+
+static void printMerged(xml::Node* node, int depth) {
+ std::string indent;
+ for (int i = 0; i < depth; i++) {
+ indent += " ";
+ }
+
+ switch (node->type) {
+ case xml::NodeType::kNamespace:
+ std::cerr << indent << "N: "
+ << "xmlns:" << static_cast<xml::Namespace*>(node)->namespacePrefix
+ << "=\"" << static_cast<xml::Namespace*>(node)->namespaceUri
+ << "\"\n";
+ break;
+
+ case xml::NodeType::kElement:
+ std::cerr << indent << "E: "
+ << static_cast<xml::Element*>(node)->namespaceUri
+ << ":" << static_cast<xml::Element*>(node)->name
+ << "\n";
+ for (const auto& attr : static_cast<xml::Element*>(node)->attributes) {
+ std::cerr << indent << " A: "
+ << attr.namespaceUri
+ << ":" << attr.name
+ << "=\"" << attr.value << "\"\n";
+ }
+ break;
+
+ case xml::NodeType::kText:
+ std::cerr << indent << "T: \"" << static_cast<xml::Text*>(node)->text << "\"\n";
+ break;
+ }
+
+ for (auto& child : node->children) {
+ printMerged(child.get(), depth + 1);
+ }
+}
+
+xml::Node* ManifestMerger::getMergedXml() {
+ return mRoot.get();
+}
+
+bool ManifestMerger::printMerged() {
+ if (!mRoot) {
+ return false;
+ }
+
+ ::aapt::printMerged(mRoot.get(), 0);
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ManifestMerger.h b/tools/aapt2/ManifestMerger.h
new file mode 100644
index 0000000..c6219db
--- /dev/null
+++ b/tools/aapt2/ManifestMerger.h
@@ -0,0 +1,45 @@
+#ifndef AAPT_MANIFEST_MERGER_H
+#define AAPT_MANIFEST_MERGER_H
+
+#include "Logger.h"
+#include "Source.h"
+#include "XmlDom.h"
+
+#include <memory>
+#include <string>
+
+namespace aapt {
+
+class ManifestMerger {
+public:
+ struct Options {
+ };
+
+ ManifestMerger(const Options& options);
+
+ bool setAppManifest(const Source& source, const std::u16string& package,
+ std::unique_ptr<xml::Node> root);
+
+ bool mergeLibraryManifest(const Source& source, const std::u16string& package,
+ std::unique_ptr<xml::Node> libRoot);
+
+ xml::Node* getMergedXml();
+
+ bool printMerged();
+
+private:
+ bool mergeNewOrEqual(xml::Element* parentA, xml::Element* elA, xml::Element* elB);
+ bool mergePreferRequired(xml::Element* parentA, xml::Element* elA, xml::Element* elB);
+ bool checkEqual(xml::Element* elA, xml::Element* elB);
+ bool mergeApplication(xml::Element* applicationA, xml::Element* applicationB);
+ bool mergeUsesSdk(xml::Element* elA, xml::Element* elB);
+
+ Options mOptions;
+ std::unique_ptr<xml::Node> mRoot;
+ SourceLogger mAppLogger;
+ SourceLogger mLogger;
+};
+
+} // namespace aapt
+
+#endif // AAPT_MANIFEST_MERGER_H
diff --git a/tools/aapt2/ManifestMerger_test.cpp b/tools/aapt2/ManifestMerger_test.cpp
new file mode 100644
index 0000000..6838253
--- /dev/null
+++ b/tools/aapt2/ManifestMerger_test.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ManifestMerger.h"
+#include "SourceXmlPullParser.h"
+
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+constexpr const char* kAppManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" />
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-feature android:name="android.hardware.GPS" android:required="false" />
+ <application android:name="com.android.library.Application">
+ <activity android:name="com.android.example.MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <service android:name="com.android.library.Service">
+ <intent-filter>
+ <action android:name="com.android.library.intent.action.SYNC" />
+ </intent-filter>
+ </service>
+ </application>
+</manifest>
+)EOF";
+
+constexpr const char* kLibManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="21" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-feature android:name="android.hardware.GPS" />
+ <uses-permission android:name="android.permission.GPS" />
+ <application android:name="com.android.library.Application">
+ <service android:name="com.android.library.Service">
+ <intent-filter>
+ <action android:name="com.android.library.intent.action.SYNC" />
+ </intent-filter>
+ </service>
+ <provider android:name="com.android.library.DocumentProvider"
+ android:authorities="com.android.library.documents"
+ android:grantUriPermission="true"
+ android:exported="true"
+ android:permission="android.permission.MANAGE_DOCUMENTS"
+ android:enabled="@bool/atLeastKitKat">
+ <intent-filter>
+ <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
+ </intent-filter>
+ </provider>
+ </application>
+</manifest>
+)EOF";
+
+constexpr const char* kBadLibManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-sdk android:minSdkVersion="17" android:targetSdkVersion="22" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-feature android:name="android.hardware.GPS" />
+ <uses-permission android:name="android.permission.GPS" />
+ <application android:name="com.android.library.Application2">
+ <service android:name="com.android.library.Service">
+ <intent-filter>
+ <action android:name="com.android.library.intent.action.SYNC_ACTION" />
+ </intent-filter>
+ </service>
+ </application>
+</manifest>
+)EOF";
+
+TEST(ManifestMergerTest, MergeManifestsSuccess) {
+ std::stringstream inA(kAppManifest);
+ std::stringstream inB(kLibManifest);
+
+ const Source sourceA = { "AndroidManifest.xml" };
+ const Source sourceB = { "lib.apk/AndroidManifest.xml" };
+ SourceLogger loggerA(sourceA);
+ SourceLogger loggerB(sourceB);
+
+ ManifestMerger merger({});
+ EXPECT_TRUE(merger.setAppManifest(sourceA, u"com.android.example",
+ xml::inflate(&inA, &loggerA)));
+ EXPECT_TRUE(merger.mergeLibraryManifest(sourceB, u"com.android.library",
+ xml::inflate(&inB, &loggerB)));
+}
+
+TEST(ManifestMergerTest, MergeManifestFail) {
+ std::stringstream inA(kAppManifest);
+ std::stringstream inB(kBadLibManifest);
+
+ const Source sourceA = { "AndroidManifest.xml" };
+ const Source sourceB = { "lib.apk/AndroidManifest.xml" };
+ SourceLogger loggerA(sourceA);
+ SourceLogger loggerB(sourceB);
+
+ ManifestMerger merger({});
+ EXPECT_TRUE(merger.setAppManifest(sourceA, u"com.android.example",
+ xml::inflate(&inA, &loggerA)));
+ EXPECT_FALSE(merger.mergeLibraryManifest(sourceB, u"com.android.library",
+ xml::inflate(&inB, &loggerB)));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ManifestParser.cpp b/tools/aapt2/ManifestParser.cpp
new file mode 100644
index 0000000..b8f0a43
--- /dev/null
+++ b/tools/aapt2/ManifestParser.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "AppInfo.h"
+#include "Logger.h"
+#include "ManifestParser.h"
+#include "Source.h"
+#include "XmlPullParser.h"
+
+#include <string>
+
+namespace aapt {
+
+bool ManifestParser::parse(const Source& source, std::shared_ptr<XmlPullParser> parser,
+ AppInfo* outInfo) {
+ SourceLogger logger = { source };
+
+ int depth = 0;
+ while (XmlPullParser::isGoodEvent(parser->next())) {
+ XmlPullParser::Event event = parser->getEvent();
+ if (event == XmlPullParser::Event::kEndElement) {
+ depth--;
+ continue;
+ } else if (event != XmlPullParser::Event::kStartElement) {
+ continue;
+ }
+
+ depth++;
+
+ const std::u16string& element = parser->getElementName();
+ if (depth == 1) {
+ if (element == u"manifest") {
+ if (!parseManifest(logger, parser, outInfo)) {
+ return false;
+ }
+ } else {
+ logger.error()
+ << "unexpected top-level element '"
+ << element
+ << "'."
+ << std::endl;
+ return false;
+ }
+ } else {
+ XmlPullParser::skipCurrentElement(parser.get());
+ }
+ }
+
+ if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
+ logger.error(parser->getLineNumber())
+ << "failed to parse manifest: "
+ << parser->getLastError()
+ << "."
+ << std::endl;
+ return false;
+ }
+ return true;
+}
+
+bool ManifestParser::parseManifest(SourceLogger& logger, std::shared_ptr<XmlPullParser> parser,
+ AppInfo* outInfo) {
+ auto attrIter = parser->findAttribute(u"", u"package");
+ if (attrIter == parser->endAttributes() || attrIter->value.empty()) {
+ logger.error() << "no 'package' attribute found for element <manifest>." << std::endl;
+ return false;
+ }
+ outInfo->package = attrIter->value;
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ManifestParser.h b/tools/aapt2/ManifestParser.h
new file mode 100644
index 0000000..f2e43d4
--- /dev/null
+++ b/tools/aapt2/ManifestParser.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_MANIFEST_PARSER_H
+#define AAPT_MANIFEST_PARSER_H
+
+#include "AppInfo.h"
+#include "Logger.h"
+#include "Source.h"
+#include "XmlPullParser.h"
+
+namespace aapt {
+
+/*
+ * Parses an AndroidManifest.xml file and fills in an AppInfo structure with
+ * app data.
+ */
+class ManifestParser {
+public:
+ ManifestParser() = default;
+ ManifestParser(const ManifestParser&) = delete;
+
+ bool parse(const Source& source, std::shared_ptr<XmlPullParser> parser, AppInfo* outInfo);
+
+private:
+ bool parseManifest(SourceLogger& logger, std::shared_ptr<XmlPullParser> parser,
+ AppInfo* outInfo);
+};
+
+} // namespace aapt
+
+#endif // AAPT_MANIFEST_PARSER_H
diff --git a/tools/aapt2/ManifestParser_test.cpp b/tools/aapt2/ManifestParser_test.cpp
new file mode 100644
index 0000000..be3a6fb
--- /dev/null
+++ b/tools/aapt2/ManifestParser_test.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "AppInfo.h"
+#include "ManifestParser.h"
+#include "SourceXmlPullParser.h"
+
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+TEST(ManifestParserTest, FindPackage) {
+ std::stringstream input;
+ input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ "package=\"android\">\n"
+ "</manifest>\n";
+
+ ManifestParser parser;
+ AppInfo info;
+ std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(input);
+ ASSERT_TRUE(parser.parse(Source{ "AndroidManifest.xml" }, xmlParser, &info));
+
+ EXPECT_EQ(std::u16string(u"android"), info.package);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ManifestValidator.cpp b/tools/aapt2/ManifestValidator.cpp
new file mode 100644
index 0000000..123b9fa
--- /dev/null
+++ b/tools/aapt2/ManifestValidator.cpp
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Logger.h"
+#include "ManifestValidator.h"
+#include "Maybe.h"
+#include "Source.h"
+#include "Util.h"
+
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+ManifestValidator::ManifestValidator(const android::ResTable& table)
+: mTable(table) {
+}
+
+bool ManifestValidator::validate(const Source& source, android::ResXMLParser* parser) {
+ SourceLogger logger(source);
+
+ android::ResXMLParser::event_code_t code;
+ while ((code = parser->next()) != android::ResXMLParser::END_DOCUMENT &&
+ code != android::ResXMLParser::BAD_DOCUMENT) {
+ if (code != android::ResXMLParser::START_TAG) {
+ continue;
+ }
+
+ size_t len = 0;
+ const StringPiece16 namespaceUri(parser->getElementNamespace(&len), len);
+ if (!namespaceUri.empty()) {
+ continue;
+ }
+
+ const StringPiece16 name(parser->getElementName(&len), len);
+ if (name.empty()) {
+ logger.error(parser->getLineNumber())
+ << "failed to get the element name."
+ << std::endl;
+ return false;
+ }
+
+ if (name == u"manifest") {
+ if (!validateManifest(source, parser)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+Maybe<StringPiece16> ManifestValidator::getAttributeValue(android::ResXMLParser* parser,
+ size_t idx) {
+ android::Res_value value;
+ if (parser->getAttributeValue(idx, &value) < 0) {
+ return StringPiece16();
+ }
+
+ const android::ResStringPool* pool = &parser->getStrings();
+ if (value.dataType == android::Res_value::TYPE_REFERENCE) {
+ ssize_t strIdx = mTable.resolveReference(&value, 0x10000000u);
+ if (strIdx < 0) {
+ return {};
+ }
+ pool = mTable.getTableStringBlock(strIdx);
+ }
+
+ if (value.dataType != android::Res_value::TYPE_STRING || !pool) {
+ return {};
+ }
+ return util::getString(*pool, value.data);
+}
+
+Maybe<StringPiece16> ManifestValidator::getAttributeInlineValue(android::ResXMLParser* parser,
+ size_t idx) {
+ android::Res_value value;
+ if (parser->getAttributeValue(idx, &value) < 0) {
+ return StringPiece16();
+ }
+
+ if (value.dataType != android::Res_value::TYPE_STRING) {
+ return {};
+ }
+ return util::getString(parser->getStrings(), value.data);
+}
+
+bool ManifestValidator::validateInlineAttribute(android::ResXMLParser* parser, size_t idx,
+ SourceLogger& logger,
+ const StringPiece16& charSet) {
+ size_t len = 0;
+ StringPiece16 element(parser->getElementName(&len), len);
+ StringPiece16 attributeName(parser->getAttributeName(idx, &len), len);
+ Maybe<StringPiece16> result = getAttributeInlineValue(parser, idx);
+ if (!result) {
+ logger.error(parser->getLineNumber())
+ << "<"
+ << element
+ << "> must have a '"
+ << attributeName
+ << "' attribute with a string literal value."
+ << std::endl;
+ return false;
+ }
+ return validateAttributeImpl(element, attributeName, result.value(), charSet,
+ parser->getLineNumber(), logger);
+}
+
+bool ManifestValidator::validateAttribute(android::ResXMLParser* parser, size_t idx,
+ SourceLogger& logger, const StringPiece16& charSet) {
+ size_t len = 0;
+ StringPiece16 element(parser->getElementName(&len), len);
+ StringPiece16 attributeName(parser->getAttributeName(idx, &len), len);
+ Maybe<StringPiece16> result = getAttributeValue(parser, idx);
+ if (!result) {
+ logger.error(parser->getLineNumber())
+ << "<"
+ << element
+ << "> must have a '"
+ << attributeName
+ << "' attribute that points to a string."
+ << std::endl;
+ return false;
+ }
+ return validateAttributeImpl(element, attributeName, result.value(), charSet,
+ parser->getLineNumber(), logger);
+}
+
+bool ManifestValidator::validateAttributeImpl(const StringPiece16& element,
+ const StringPiece16& attributeName,
+ const StringPiece16& attributeValue,
+ const StringPiece16& charSet, size_t lineNumber,
+ SourceLogger& logger) {
+ StringPiece16::const_iterator badIter =
+ util::findNonAlphaNumericAndNotInSet(attributeValue, charSet);
+ if (badIter != attributeValue.end()) {
+ logger.error(lineNumber)
+ << "tag <"
+ << element
+ << "> attribute '"
+ << attributeName
+ << "' has invalid character '"
+ << StringPiece16(badIter, 1)
+ << "'."
+ << std::endl;
+ return false;
+ }
+
+ if (!attributeValue.empty()) {
+ StringPiece16 trimmed = util::trimWhitespace(attributeValue);
+ if (attributeValue.begin() != trimmed.begin()) {
+ logger.error(lineNumber)
+ << "tag <"
+ << element
+ << "> attribute '"
+ << attributeName
+ << "' can not start with whitespace."
+ << std::endl;
+ return false;
+ }
+
+ if (attributeValue.end() != trimmed.end()) {
+ logger.error(lineNumber)
+ << "tag <"
+ << element
+ << "> attribute '"
+ << attributeName
+ << "' can not end with whitespace."
+ << std::endl;
+ return false;
+ }
+ }
+ return true;
+}
+
+constexpr const char16_t* kPackageIdentSet = u"._";
+
+bool ManifestValidator::validateManifest(const Source& source, android::ResXMLParser* parser) {
+ bool error = false;
+ SourceLogger logger(source);
+
+ const StringPiece16 kAndroid = u"android";
+ const StringPiece16 kPackage = u"package";
+ const StringPiece16 kSharedUserId = u"sharedUserId";
+
+ ssize_t idx;
+
+ idx = parser->indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size());
+ if (idx < 0) {
+ logger.error(parser->getLineNumber())
+ << "missing package attribute."
+ << std::endl;
+ error = true;
+ } else {
+ error |= !validateInlineAttribute(parser, idx, logger, kPackageIdentSet);
+ }
+
+ idx = parser->indexOfAttribute(kAndroid.data(), kAndroid.size(),
+ kSharedUserId.data(), kSharedUserId.size());
+ if (idx >= 0) {
+ error |= !validateInlineAttribute(parser, idx, logger, kPackageIdentSet);
+ }
+ return !error;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ManifestValidator.h b/tools/aapt2/ManifestValidator.h
new file mode 100644
index 0000000..3188784
--- /dev/null
+++ b/tools/aapt2/ManifestValidator.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_MANIFEST_VALIDATOR_H
+#define AAPT_MANIFEST_VALIDATOR_H
+
+#include "Logger.h"
+#include "Maybe.h"
+#include "Source.h"
+#include "StringPiece.h"
+
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+class ManifestValidator {
+public:
+ ManifestValidator(const android::ResTable& table);
+ ManifestValidator(const ManifestValidator&) = delete;
+
+ bool validate(const Source& source, android::ResXMLParser* parser);
+
+private:
+ bool validateManifest(const Source& source, android::ResXMLParser* parser);
+
+ Maybe<StringPiece16> getAttributeInlineValue(android::ResXMLParser* parser, size_t idx);
+ Maybe<StringPiece16> getAttributeValue(android::ResXMLParser* parser, size_t idx);
+
+ bool validateInlineAttribute(android::ResXMLParser* parser, size_t idx,
+ SourceLogger& logger, const StringPiece16& charSet);
+ bool validateAttribute(android::ResXMLParser* parser, size_t idx, SourceLogger& logger,
+ const StringPiece16& charSet);
+ bool validateAttributeImpl(const StringPiece16& element, const StringPiece16& attributeName,
+ const StringPiece16& attributeValue, const StringPiece16& charSet,
+ size_t lineNumber, SourceLogger& logger);
+
+ const android::ResTable& mTable;
+};
+
+} // namespace aapt
+
+#endif // AAPT_MANIFEST_VALIDATOR_H
diff --git a/tools/aapt2/Maybe.h b/tools/aapt2/Maybe.h
new file mode 100644
index 0000000..ff6625f
--- /dev/null
+++ b/tools/aapt2/Maybe.h
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_MAYBE_H
+#define AAPT_MAYBE_H
+
+#include <cassert>
+#include <type_traits>
+#include <utility>
+
+namespace aapt {
+
+/**
+ * Either holds a valid value of type T, or holds Nothing.
+ * The value is stored inline in this structure, so no
+ * heap memory is used when creating a Maybe<T> object.
+ */
+template <typename T>
+class Maybe {
+public:
+ /**
+ * Construct Nothing.
+ */
+ Maybe();
+
+ ~Maybe();
+
+ Maybe(const Maybe& rhs);
+
+ template <typename U>
+ Maybe(const Maybe<U>& rhs);
+
+ Maybe(Maybe&& rhs);
+
+ template <typename U>
+ Maybe(Maybe<U>&& rhs);
+
+ Maybe& operator=(const Maybe& rhs);
+
+ template <typename U>
+ Maybe& operator=(const Maybe<U>& rhs);
+
+ Maybe& operator=(Maybe&& rhs);
+
+ template <typename U>
+ Maybe& operator=(Maybe<U>&& rhs);
+
+ /**
+ * Construct a Maybe holding a value.
+ */
+ Maybe(const T& value);
+
+ /**
+ * Construct a Maybe holding a value.
+ */
+ Maybe(T&& value);
+
+ /**
+ * True if this holds a value, false if
+ * it holds Nothing.
+ */
+ operator bool() const;
+
+ /**
+ * Gets the value if one exists, or else
+ * panics.
+ */
+ T& value();
+
+ /**
+ * Gets the value if one exists, or else
+ * panics.
+ */
+ const T& value() const;
+
+private:
+ template <typename U>
+ friend class Maybe;
+
+ template <typename U>
+ Maybe& copy(const Maybe<U>& rhs);
+
+ template <typename U>
+ Maybe& move(Maybe<U>&& rhs);
+
+ void destroy();
+
+ bool mNothing;
+
+ typename std::aligned_storage<sizeof(T), alignof(T)>::type mStorage;
+};
+
+template <typename T>
+Maybe<T>::Maybe()
+: mNothing(true) {
+}
+
+template <typename T>
+Maybe<T>::~Maybe() {
+ if (!mNothing) {
+ 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));
+ }
+}
+
+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));
+ }
+}
+
+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();
+ }
+}
+
+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();
+ }
+}
+
+template <typename T>
+inline Maybe<T>& Maybe<T>::operator=(const Maybe& 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);
+}
+
+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();
+ }
+ 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));
+}
+
+template <typename T>
+template <typename U>
+inline Maybe<T>& Maybe<T>::operator=(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();
+ }
+ return *this;
+}
+
+template <typename T>
+Maybe<T>::Maybe(const T& value)
+: mNothing(false) {
+ new (&mStorage) T(value);
+}
+
+template <typename T>
+Maybe<T>::Maybe(T&& value)
+: mNothing(false) {
+ new (&mStorage) T(std::forward<T>(value));
+}
+
+template <typename T>
+Maybe<T>::operator bool() const {
+ return !mNothing;
+}
+
+template <typename T>
+T& Maybe<T>::value() {
+ assert(!mNothing && "Maybe<T>::value() called on Nothing");
+ return reinterpret_cast<T&>(mStorage);
+}
+
+template <typename T>
+const T& Maybe<T>::value() const {
+ assert(!mNothing && "Maybe<T>::value() called on Nothing");
+ return reinterpret_cast<const T&>(mStorage);
+}
+
+template <typename T>
+void Maybe<T>::destroy() {
+ reinterpret_cast<T&>(mStorage).~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));
+}
+
+template <typename T>
+inline Maybe<T> make_nothing() {
+ return Maybe<T>();
+}
+
+} // namespace aapt
+
+#endif // AAPT_MAYBE_H
diff --git a/tools/aapt2/Maybe_test.cpp b/tools/aapt2/Maybe_test.cpp
new file mode 100644
index 0000000..71bbb94
--- /dev/null
+++ b/tools/aapt2/Maybe_test.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <string>
+
+#include "Maybe.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() {
+ 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>();
+ EXPECT_FALSE(val);
+
+ Maybe<std::string> val2 = make_nothing<std::string>();
+ EXPECT_FALSE(val2);
+
+ val2 = make_nothing<std::string>();
+ EXPECT_FALSE(val2);
+}
+
+TEST(MaybeTest, MakeSomething) {
+ Maybe<int> val = make_value(23);
+ ASSERT_TRUE(val);
+ EXPECT_EQ(23, val.value());
+
+ Maybe<std::string> val2 = make_value(std::string("hey"));
+ ASSERT_TRUE(val2);
+ EXPECT_EQ(std::string("hey"), val2.value());
+}
+
+TEST(MaybeTest, Lifecycle) {
+ Maybe<Dummy> val = make_nothing<Dummy>();
+
+ Maybe<Dummy> val2 = make_value(Dummy());
+}
+
+TEST(MaybeTest, MoveAssign) {
+ Maybe<Dummy> val;
+ {
+ Maybe<Dummy> val2 = Dummy();
+ val = std::move(val2);
+ }
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/MockResolver.h b/tools/aapt2/MockResolver.h
new file mode 100644
index 0000000..0c9b954
--- /dev/null
+++ b/tools/aapt2/MockResolver.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_MOCK_RESOLVER_H
+#define AAPT_MOCK_RESOLVER_H
+
+#include "Maybe.h"
+#include "Resolver.h"
+#include "Resource.h"
+#include "ResourceTable.h"
+#include "ResourceTableResolver.h"
+#include "ResourceValues.h"
+#include "StringPiece.h"
+
+#include <map>
+#include <string>
+
+namespace aapt {
+
+struct MockResolver : public IResolver {
+ MockResolver(const std::shared_ptr<ResourceTable>& table,
+ const std::map<ResourceName, ResourceId>& items) :
+ mResolver(std::make_shared<ResourceTableResolver>(
+ table, std::vector<std::shared_ptr<const android::AssetManager>>())),
+ mAttr(false, android::ResTable_map::TYPE_ANY), mItems(items) {
+ }
+
+ virtual Maybe<ResourceId> findId(const ResourceName& name) override {
+ Maybe<ResourceId> result = mResolver->findId(name);
+ if (result) {
+ return result;
+ }
+
+ const auto iter = mItems.find(name);
+ if (iter != mItems.end()) {
+ return iter->second;
+ }
+ return {};
+ }
+
+ virtual Maybe<Entry> findAttribute(const ResourceName& name) override {
+ Maybe<Entry> tableResult = mResolver->findAttribute(name);
+ if (tableResult) {
+ return tableResult;
+ }
+
+ Maybe<ResourceId> result = findId(name);
+ if (result) {
+ if (name.type == ResourceType::kAttr) {
+ return Entry{ result.value(), &mAttr };
+ } else {
+ return Entry{ result.value() };
+ }
+ }
+ return {};
+ }
+
+ virtual Maybe<ResourceName> findName(ResourceId resId) override {
+ Maybe<ResourceName> result = mResolver->findName(resId);
+ if (result) {
+ return result;
+ }
+
+ for (auto& p : mItems) {
+ if (p.second == resId) {
+ return p.first;
+ }
+ }
+ return {};
+ }
+
+private:
+ std::shared_ptr<ResourceTableResolver> mResolver;
+ Attribute mAttr;
+ std::map<ResourceName, ResourceId> mItems;
+};
+
+} // namespace aapt
+
+#endif // AAPT_MOCK_RESOLVER_H
diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h
new file mode 100644
index 0000000..1e15e20
--- /dev/null
+++ b/tools/aapt2/NameMangler.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_NAME_MANGLER_H
+#define AAPT_NAME_MANGLER_H
+
+#include <string>
+
+namespace aapt {
+
+struct NameMangler {
+ /**
+ * Mangles the name in `outName` with the `package` and stores the mangled
+ * result in `outName`. The mangled name should contain symbols that are
+ * illegal to define in XML, so that there will never be name mangling
+ * collisions.
+ */
+ static void mangle(const std::u16string& package, std::u16string* outName) {
+ *outName = package + u"$" + *outName;
+ }
+
+ /**
+ * 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;
+ }
+};
+
+} // namespace aapt
+
+#endif // AAPT_NAME_MANGLER_H
diff --git a/tools/aapt2/NameMangler_test.cpp b/tools/aapt2/NameMangler_test.cpp
new file mode 100644
index 0000000..6103655
--- /dev/null
+++ b/tools/aapt2/NameMangler_test.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "NameMangler.h"
+
+#include <gtest/gtest.h>
+#include <string>
+
+namespace aapt {
+
+TEST(NameManglerTest, MangleName) {
+ std::u16string package = u"android.appcompat";
+ std::u16string name = u"Platform.AppCompat";
+
+ NameMangler::mangle(package, &name);
+ EXPECT_EQ(name, u"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");
+}
+
+TEST(NameManglerTest, IgnoreUnmangledName) {
+ std::u16string package;
+ std::u16string name = u"foo_bar";
+
+ EXPECT_FALSE(NameMangler::unmangle(&name, &package));
+ EXPECT_EQ(name, u"foo_bar");
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Png.cpp b/tools/aapt2/Png.cpp
new file mode 100644
index 0000000..4e9b68e
--- /dev/null
+++ b/tools/aapt2/Png.cpp
@@ -0,0 +1,1280 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BigBuffer.h"
+#include "Logger.h"
+#include "Png.h"
+#include "Source.h"
+#include "Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <iostream>
+#include <png.h>
+#include <sstream>
+#include <string>
+#include <vector>
+#include <zlib.h>
+
+namespace aapt {
+
+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;
+};
+
+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 flushDataToStream(png_structp /*writePtr*/) {
+}
+
+static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
+ SourceLogger* logger = reinterpret_cast<SourceLogger*>(png_get_error_ptr(readPtr));
+ logger->warn() << warningMessage << "." << std::endl;
+}
+
+
+static bool readPng(png_structp readPtr, png_infop infoPtr, PngInfo* outInfo,
+ std::string* outError) {
+ if (setjmp(png_jmpbuf(readPtr))) {
+ *outError = "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 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) {
+ 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) {
+ // We use a padding byte even when there is no alpha
+ bpp = 4;
+ } else {
+ printf("Unknown color type %d.\n", color_type);
+ }
+
+ for (j = 0; j < h; j++) {
+ const png_byte* row = rows[j];
+ for (i = 0; i < w; i++) {
+ rr = row[0];
+ gg = row[1];
+ bb = row[2];
+ aa = row[3];
+ row += bpp;
+
+ if (i == 0) {
+ printf("Row %d:", j);
+ }
+ switch (bpp) {
+ case 1:
+ printf(" (%d)", rr);
+ break;
+ case 2:
+ printf(" (%d %d", rr, gg);
+ break;
+ case 3:
+ printf(" (%d %d %d)", rr, gg, bb);
+ break;
+ case 4:
+ printf(" (%d %d %d %d)", rr, gg, bb, aa);
+ break;
+ }
+ if (i == (w - 1)) {
+ printf("\n");
+ }
+ }
+ }
+}*/
+
+#define MAX(a,b) ((a)>(b)?(a):(b))
+#define ABS(a) ((a)<0?-(a):(a))
+
+static void analyze_image(SourceLogger* logger, const PngInfo& imageInfo, int grayscaleTolerance,
+ 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) {
+ logger->note() << "forcing image to gray (max deviation = " << maxGrayDeviation
+ << ")."
+ << std::endl;
+ *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
+
+ if (*colorType == PNG_COLOR_TYPE_PALETTE) {
+ // Create separate RGB and Alpha palettes and set the number of colors
+ *paletteEntries = num_colors;
+
+ // 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);
+ }
+ } 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;
+ }
+ }
+ }
+ }
+}
+
+static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info,
+ int grayScaleTolerance, SourceLogger* logger, std::string* outError) {
+ if (setjmp(png_jmpbuf(writePtr))) {
+ *outError = "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) {
+ logger->note() << "writing image: w = " << info->width
+ << ", h = " << info->height
+ << std::endl;
+ }
+
+ png_color rgbPalette[256];
+ png_byte alphaPalette[256];
+ bool hasTransparency;
+ int paletteEntries;
+
+ analyze_image(logger, *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:
+ logger->note() << "has " << paletteEntries
+ << " colors" << (hasTransparency ? " (with alpha)" : "")
+ << ", using PNG_COLOR_TYPE_PALLETTE."
+ << std::endl;
+ break;
+ case PNG_COLOR_TYPE_GRAY:
+ logger->note() << "is opaque gray, using PNG_COLOR_TYPE_GRAY." << std::endl;
+ break;
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ logger->note() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA." << std::endl;
+ break;
+ case PNG_COLOR_TYPE_RGB:
+ logger->note() << "is opaque RGB, using PNG_COLOR_TYPE_RGB." << std::endl;
+ break;
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ logger->note() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA." << std::endl;
+ 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) {
+ logger->note() << "adding 9-patch info..." << std::endl;
+ }
+ 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);
+
+#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
+ }
+
+ 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) {
+ logger->note() << "image written: w = " << width << ", h = " << height
+ << ", d = " << bitDepth << ", colors = " << colorType
+ << ", inter = " << interlaceType << ", comp = " << compressionType
+ << std::endl;
+ }
+ return true;
+}
+
+constexpr uint32_t kColorWhite = 0xffffffffu;
+constexpr uint32_t kColorTick = 0xff000000u;
+constexpr uint32_t kColorLayoutBoundsTick = 0xff0000ffu;
+
+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;
+ }
+
+ if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
+ *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
+};
+
+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;
+ }
+ }
+
+ 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;
+ }
+ }
+
+ if (required && !found) {
+ *outError = "No marked region found along edge";
+ *outTop = -1;
+ 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;
+ }
+ }
+ }
+
+ // 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,
+ 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;
+}
+
+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;
+}
+
+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);
+ }
+}
+
+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;
+ }
+
+ 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;
+ }
+ }
+ 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];
+ } 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;
+ }
+ top = bottom;
+ }
+
+ 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");
+ }
+ }
+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;
+}
+
+
+bool Png::process(const Source& source, std::istream& input, BigBuffer* outBuffer,
+ const Options& options, std::string* outError) {
+ png_byte signature[kPngSignatureSize];
+
+ // Read the PNG signature first.
+ if (!input.read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
+ *outError = strerror(errno);
+ return false;
+ }
+
+ // If the PNG signature doesn't match, bail early.
+ if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
+ *outError = "not a valid png file";
+ return false;
+ }
+
+ SourceLogger logger(source);
+ 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) {
+ *outError = "failed to allocate read ptr";
+ goto bail;
+ }
+
+ infoPtr = png_create_info_struct(readPtr);
+ if (!infoPtr) {
+ *outError = "failed to allocate info ptr";
+ goto bail;
+ }
+
+ png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(&logger), nullptr, logWarning);
+
+ // Set the read function to read from std::istream.
+ png_set_read_fn(readPtr, (png_voidp)&input, readDataFromStream);
+
+ if (!readPng(readPtr, infoPtr, &pngInfo, outError)) {
+ goto bail;
+ }
+
+ if (util::stringEndsWith<char>(source.path, ".9.png")) {
+ if (!do9Patch(&pngInfo, outError)) {
+ goto bail;
+ }
+ }
+
+ writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
+ if (!writePtr) {
+ *outError = "failed to allocate write ptr";
+ goto bail;
+ }
+
+ writeInfoPtr = png_create_info_struct(writePtr);
+ if (!writeInfoPtr) {
+ *outError = "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(writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance, &logger,
+ outError)) {
+ goto bail;
+ }
+
+ result = true;
+bail:
+ if (readPtr) {
+ png_destroy_read_struct(&readPtr, &infoPtr, nullptr);
+ }
+
+ if (writePtr) {
+ png_destroy_write_struct(&writePtr, &writeInfoPtr);
+ }
+ return result;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Png.h b/tools/aapt2/Png.h
new file mode 100644
index 0000000..4577ab8
--- /dev/null
+++ b/tools/aapt2/Png.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_PNG_H
+#define AAPT_PNG_H
+
+#include "BigBuffer.h"
+#include "Source.h"
+
+#include <iostream>
+#include <string>
+
+namespace aapt {
+
+struct Png {
+ struct Options {
+ int grayScaleTolerance = 0;
+ };
+
+ bool process(const Source& source, std::istream& input, BigBuffer* outBuffer,
+ const Options& options, std::string* outError);
+};
+
+} // namespace aapt
+
+#endif // AAPT_PNG_H
diff --git a/tools/aapt2/ProguardRules.cpp b/tools/aapt2/ProguardRules.cpp
new file mode 100644
index 0000000..e89fb7c
--- /dev/null
+++ b/tools/aapt2/ProguardRules.cpp
@@ -0,0 +1,240 @@
+/*
+ * 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 "ProguardRules.h"
+#include "Util.h"
+#include "XmlDom.h"
+
+#include <memory>
+#include <string>
+
+namespace aapt {
+namespace proguard {
+
+constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
+
+class BaseVisitor : public xml::Visitor {
+public:
+ BaseVisitor(const Source& source, KeepSet* keepSet) : mSource(source), mKeepSet(keepSet) {
+ }
+
+ 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::Element* node) override {
+ if (!node->namespaceUri.empty()) {
+ Maybe<std::u16string> maybePackage = util::extractPackageFromNamespace(
+ node->namespaceUri);
+ if (maybePackage) {
+ // This is a custom view, let's figure out the class name from this.
+ std::u16string package = maybePackage.value() + u"." + node->name;
+ 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);
+ }
+ }
+
+protected:
+ void addClass(size_t lineNumber, const std::u16string& className) {
+ mKeepSet->addClass(mSource.line(lineNumber), className);
+ }
+
+ void addMethod(size_t lineNumber, const std::u16string& methodName) {
+ mKeepSet->addMethod(mSource.line(lineNumber), methodName);
+ }
+
+private:
+ Source mSource;
+ KeepSet* mKeepSet;
+};
+
+struct LayoutVisitor : public BaseVisitor {
+ LayoutVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+ }
+
+ 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 == kSchemaAndroid) {
+ checkName = node->name == u"fragment";
+ }
+
+ 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 == kSchemaAndroid && attr.name == u"name" &&
+ util::isJavaClassName(attr.value)) {
+ addClass(node->lineNumber, attr.value);
+ } else if (attr.namespaceUri == kSchemaAndroid && attr.name == u"onClick") {
+ addMethod(node->lineNumber, attr.value);
+ }
+ }
+
+ BaseVisitor::visit(node);
+ }
+};
+
+struct XmlResourceVisitor : public BaseVisitor {
+ XmlResourceVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+ }
+
+ virtual void visit(xml::Element* node) override {
+ bool checkFragment = false;
+ if (node->namespaceUri.empty()) {
+ checkFragment = node->name == u"PreferenceScreen" || node->name == u"header";
+ }
+
+ if (checkFragment) {
+ xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"fragment");
+ if (attr && util::isJavaClassName(attr->value)) {
+ addClass(node->lineNumber, attr->value);
+ }
+ }
+
+ BaseVisitor::visit(node);
+ }
+};
+
+struct TransitionVisitor : public BaseVisitor {
+ TransitionVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+ }
+
+ 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);
+ }
+};
+
+struct ManifestVisitor : public BaseVisitor {
+ ManifestVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+ }
+
+ 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(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(kSchemaAndroid, u"name");
+ if (attr) {
+ Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage,
+ attr->value);
+ if (result) {
+ addClass(node->lineNumber, result.value());
+ }
+ }
+ }
+ }
+ BaseVisitor::visit(node);
+ }
+
+ std::u16string mPackage;
+};
+
+bool collectProguardRulesForManifest(const Source& source, xml::Node* node, KeepSet* keepSet) {
+ ManifestVisitor visitor(source, keepSet);
+ node->accept(&visitor);
+ return true;
+}
+
+bool collectProguardRules(ResourceType type, const Source& source, xml::Node* node,
+ KeepSet* keepSet) {
+ switch (type) {
+ case ResourceType::kLayout: {
+ LayoutVisitor visitor(source, keepSet);
+ node->accept(&visitor);
+ break;
+ }
+
+ case ResourceType::kXml: {
+ XmlResourceVisitor visitor(source, keepSet);
+ node->accept(&visitor);
+ break;
+ }
+
+ case ResourceType::kTransition: {
+ TransitionVisitor visitor(source, keepSet);
+ node->accept(&visitor);
+ break;
+ }
+
+ default:
+ break;
+ }
+ return true;
+}
+
+bool writeKeepSet(std::ostream* out, const KeepSet& keepSet) {
+ for (const auto& entry : keepSet.mKeepSet) {
+ for (const SourceLine& source : entry.second) {
+ *out << "// Referenced at " << source << "\n";
+ }
+ *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
+ }
+
+ for (const auto& entry : keepSet.mKeepMethodSet) {
+ for (const SourceLine& source : entry.second) {
+ *out << "// Referenced at " << source << "\n";
+ }
+ *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl;
+ }
+ return true;
+}
+
+} // namespace proguard
+} // namespace aapt
diff --git a/tools/aapt2/ProguardRules.h b/tools/aapt2/ProguardRules.h
new file mode 100644
index 0000000..bbb3e64
--- /dev/null
+++ b/tools/aapt2/ProguardRules.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_PROGUARD_RULES_H
+#define AAPT_PROGUARD_RULES_H
+
+#include "Resource.h"
+#include "Source.h"
+#include "XmlDom.h"
+
+#include <map>
+#include <ostream>
+#include <set>
+#include <string>
+
+namespace aapt {
+namespace proguard {
+
+class KeepSet {
+public:
+ inline void addClass(const SourceLine& source, const std::u16string& className) {
+ mKeepSet[className].insert(source);
+ }
+
+ inline void addMethod(const SourceLine& source, const std::u16string& methodName) {
+ mKeepMethodSet[methodName].insert(source);
+ }
+
+private:
+ friend bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
+
+ std::map<std::u16string, std::set<SourceLine>> mKeepSet;
+ std::map<std::u16string, std::set<SourceLine>> mKeepMethodSet;
+};
+
+bool collectProguardRulesForManifest(const Source& source, xml::Node* node, KeepSet* keepSet);
+bool collectProguardRules(ResourceType type, const Source& source, xml::Node* node,
+ KeepSet* keepSet);
+
+bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
+
+} // namespace proguard
+} // namespace aapt
+
+#endif // AAPT_PROGUARD_RULES_H
diff --git a/tools/aapt2/ResChunkPullParser.cpp b/tools/aapt2/ResChunkPullParser.cpp
new file mode 100644
index 0000000..78ea60e
--- /dev/null
+++ b/tools/aapt2/ResChunkPullParser.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResChunkPullParser.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <cstddef>
+
+namespace aapt {
+
+using android::ResChunk_header;
+
+ResChunkPullParser::Event ResChunkPullParser::next() {
+ if (!isGoodEvent(mEvent)) {
+ return mEvent;
+ }
+
+ if (mEvent == Event::StartDocument) {
+ mCurrentChunk = mData;
+ } else {
+ mCurrentChunk = reinterpret_cast<const ResChunk_header*>(
+ reinterpret_cast<const char*>(mCurrentChunk) + mCurrentChunk->size);
+ }
+
+ const std::ptrdiff_t diff = reinterpret_cast<const char*>(mCurrentChunk)
+ - reinterpret_cast<const char*>(mData);
+ assert(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 (mCurrentChunk->headerSize < sizeof(ResChunk_header)) {
+ mLastError = "chunk has too small header";
+ mCurrentChunk = nullptr;
+ return (mEvent = Event::BadDocument);
+ } else if (mCurrentChunk->size < mCurrentChunk->headerSize) {
+ mLastError = "chunk's total size is smaller than header";
+ mCurrentChunk = nullptr;
+ return (mEvent = Event::BadDocument);
+ } else if (offset + mCurrentChunk->size > mLen) {
+ mLastError = "chunk's data extends past the end of the document";
+ mCurrentChunk = nullptr;
+ return (mEvent = Event::BadDocument);
+ }
+ return (mEvent = Event::Chunk);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResChunkPullParser.h b/tools/aapt2/ResChunkPullParser.h
new file mode 100644
index 0000000..1426ed2
--- /dev/null
+++ b/tools/aapt2/ResChunkPullParser.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_RES_CHUNK_PULL_PARSER_H
+#define AAPT_RES_CHUNK_PULL_PARSER_H
+
+#include <androidfw/ResourceTypes.h>
+#include <string>
+
+namespace aapt {
+
+/**
+ * A pull parser, modeled after XmlPullParser, that reads
+ * android::ResChunk_header structs from a block of data.
+ *
+ * An android::ResChunk_header specifies a type, headerSize,
+ * and size. The pull parser will verify that the chunk's size
+ * doesn't extend beyond the available data, and will iterate
+ * over each chunk in the given block of data.
+ *
+ * Processing nested chunks is done by creating a new ResChunkPullParser
+ * 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;
+};
+
+template <typename T>
+inline static const T* convertTo(const android::ResChunk_header* chunk) {
+ if (chunk->headerSize < sizeof(T)) {
+ return nullptr;
+ }
+ return reinterpret_cast<const T*>(chunk);
+}
+
+inline static const uint8_t* getChunkData(const android::ResChunk_header& chunk) {
+ return reinterpret_cast<const uint8_t*>(&chunk) + chunk.headerSize;
+}
+
+inline static size_t getChunkDataLen(const android::ResChunk_header& chunk) {
+ return chunk.size - chunk.headerSize;
+}
+
+//
+// Implementation
+//
+
+inline bool ResChunkPullParser::isGoodEvent(ResChunkPullParser::Event event) {
+ return event != Event::EndDocument && event != Event::BadDocument;
+}
+
+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::Event ResChunkPullParser::getEvent() const {
+ return mEvent;
+}
+
+inline const std::string& ResChunkPullParser::getLastError() const {
+ return mLastError;
+}
+
+inline const android::ResChunk_header* ResChunkPullParser::getChunk() const {
+ return mCurrentChunk;
+}
+
+} // namespace aapt
+
+#endif // AAPT_RES_CHUNK_PULL_PARSER_H
diff --git a/tools/aapt2/Resolver.h b/tools/aapt2/Resolver.h
new file mode 100644
index 0000000..cb9318e
--- /dev/null
+++ b/tools/aapt2/Resolver.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_RESOLVER_H
+#define AAPT_RESOLVER_H
+
+#include "Maybe.h"
+#include "Resource.h"
+#include "ResourceValues.h"
+
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+/**
+ * Resolves symbolic references (package:type/entry) into resource IDs/objects.
+ */
+class IResolver {
+public:
+ virtual ~IResolver() {};
+
+ /**
+ * Holds the result of a resource name lookup.
+ */
+ struct Entry {
+ /**
+ * The ID of the resource. ResourceId::isValid() may
+ * return false if the resource has not been assigned
+ * an ID.
+ */
+ ResourceId id;
+
+ /**
+ * If the resource is an attribute, this will point
+ * to a valid Attribute object, or else it will be
+ * nullptr.
+ */
+ const Attribute* attr;
+ };
+
+ /**
+ * Returns a ResourceID if the name is found. The ResourceID
+ * may not be valid if the resource was not assigned an ID.
+ */
+ virtual Maybe<ResourceId> findId(const ResourceName& name) = 0;
+
+ /**
+ * Returns an Entry if the name is found. Entry::attr
+ * may be nullptr if the resource is not an attribute.
+ */
+ virtual Maybe<Entry> findAttribute(const ResourceName& name) = 0;
+
+ /**
+ * Find a resource by ID. Resolvers may contain resources without
+ * resource IDs assigned to them.
+ */
+ virtual Maybe<ResourceName> findName(ResourceId resId) = 0;
+};
+
+} // namespace aapt
+
+#endif // AAPT_RESOLVER_H
diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp
new file mode 100644
index 0000000..287d8de
--- /dev/null
+++ b/tools/aapt2/Resource.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Resource.h"
+#include "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";
+ 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::kIntegerArray: return u"integer-array";
+ 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 {};
+}
+
+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"integer-array", ResourceType::kIntegerArray },
+ { 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 },
+};
+
+const ResourceType* parseResourceType(const StringPiece16& str) {
+ auto iter = sResourceTypeMap.find(str);
+ if (iter == std::end(sResourceTypeMap)) {
+ return nullptr;
+ }
+ return &iter->second;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
new file mode 100644
index 0000000..fa9ac07
--- /dev/null
+++ b/tools/aapt2/Resource.h
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_RESOURCE_H
+#define AAPT_RESOURCE_H
+
+#include "StringPiece.h"
+
+#include <iomanip>
+#include <limits>
+#include <string>
+#include <tuple>
+
+namespace aapt {
+
+/**
+ * The various types of resource types available. Corresponds
+ * to the 'type' in package:type/entry.
+ */
+enum class ResourceType {
+ kAnim,
+ kAnimator,
+ kArray,
+ kAttr,
+ kAttrPrivate,
+ kBool,
+ kColor,
+ kDimen,
+ kDrawable,
+ kFraction,
+ kId,
+ kInteger,
+ kIntegerArray,
+ kInterpolator,
+ kLayout,
+ kMenu,
+ kMipmap,
+ kPlurals,
+ kRaw,
+ kString,
+ kStyle,
+ kStyleable,
+ kTransition,
+ kXml,
+};
+
+StringPiece16 toString(ResourceType type);
+
+/**
+ * Returns a pointer to a valid ResourceType, or nullptr if
+ * the string was invalid.
+ */
+const ResourceType* parseResourceType(const StringPiece16& str);
+
+/**
+ * A resource's name. This can uniquely identify
+ * a resource in the ResourceTable.
+ */
+struct ResourceName {
+ std::u16string package;
+ ResourceType type;
+ std::u16string entry;
+
+ bool isValid() const;
+ bool operator<(const ResourceName& rhs) const;
+ bool operator==(const ResourceName& rhs) const;
+ bool operator!=(const ResourceName& rhs) const;
+};
+
+/**
+ * Same as ResourceName, but uses StringPieces instead.
+ * Use this if you need to avoid copying and know that
+ * the lifetime of this object is shorter than that
+ * 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);
+ 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;
+
+ bool operator<(const ResourceNameRef& rhs) const;
+ bool operator==(const ResourceNameRef& rhs) const;
+ bool operator!=(const ResourceNameRef& rhs) const;
+};
+
+/**
+ * A binary identifier representing a resource. Internally it
+ * is a 32bit integer split as follows:
+ *
+ * 0xPPTTEEEE
+ *
+ * PP: 8 bit package identifier. 0x01 is reserved for system
+ * and 0x7f is reserved for the running app.
+ * TT: 8 bit type identifier. 0x00 is invalid.
+ * EEEE: 16 bit entry identifier.
+ */
+struct ResourceId {
+ uint32_t id;
+
+ ResourceId();
+ ResourceId(const ResourceId& rhs);
+ ResourceId(uint32_t resId);
+ ResourceId(size_t p, size_t t, size_t e);
+
+ bool isValid() const;
+ uint8_t packageId() const;
+ uint8_t typeId() const;
+ uint16_t entryId() const;
+ bool operator<(const ResourceId& rhs) const;
+ bool operator==(const ResourceId& rhs) const;
+};
+
+//
+// ResourceId implementation.
+//
+
+inline ResourceId::ResourceId() : id(0) {
+}
+
+inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) {
+}
+
+inline ResourceId::ResourceId(uint32_t resId) : id(resId) {
+}
+
+inline ResourceId::ResourceId(size_t p, size_t t, size_t e) : id(0) {
+ if (p > std::numeric_limits<uint8_t>::max() ||
+ t > std::numeric_limits<uint8_t>::max() ||
+ e > std::numeric_limits<uint16_t>::max()) {
+ // This will leave the ResourceId in an invalid state.
+ return;
+ }
+
+ id = (static_cast<uint8_t>(p) << 24) |
+ (static_cast<uint8_t>(t) << 16) |
+ static_cast<uint16_t>(e);
+}
+
+inline bool ResourceId::isValid() 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::typeId() const {
+ return static_cast<uint8_t>(id >> 16);
+}
+
+inline uint16_t ResourceId::entryId() const {
+ return static_cast<uint16_t>(id);
+}
+
+inline bool ResourceId::operator<(const ResourceId& rhs) const {
+ return id < rhs.id;
+}
+
+inline bool ResourceId::operator==(const ResourceId& rhs) const {
+ return 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;
+}
+
+//
+// ResourceType implementation.
+//
+
+inline ::std::ostream& operator<<(::std::ostream& out, const ResourceType& val) {
+ return out << toString(val);
+}
+
+//
+// ResourceName implementation.
+//
+
+inline bool ResourceName::isValid() const {
+ return !package.empty() && !entry.empty();
+}
+
+inline bool ResourceName::operator<(const ResourceName& rhs) const {
+ return std::tie(package, type, entry)
+ < std::tie(rhs.package, rhs.type, rhs.entry);
+}
+
+inline bool ResourceName::operator==(const ResourceName& rhs) const {
+ return std::tie(package, type, entry)
+ == std::tie(rhs.package, rhs.type, rhs.entry);
+}
+
+inline bool ResourceName::operator!=(const ResourceName& rhs) const {
+ return std::tie(package, type, entry)
+ != std::tie(rhs.package, rhs.type, rhs.entry);
+}
+
+inline ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) {
+ if (!name.package.empty()) {
+ out << name.package << ":";
+ }
+ return out << name.type << "/" << name.entry;
+}
+
+
+//
+// ResourceNameRef implementation.
+//
+
+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::operator=(const ResourceName& rhs) {
+ package = rhs.package;
+ type = rhs.type;
+ entry = rhs.entry;
+ return *this;
+}
+
+inline ResourceName ResourceNameRef::toResourceName() const {
+ return { package.toString(), type, entry.toString() };
+}
+
+inline bool ResourceNameRef::isValid() const {
+ return !package.empty() && !entry.empty();
+}
+
+inline bool ResourceNameRef::operator<(const ResourceNameRef& rhs) const {
+ return std::tie(package, type, entry)
+ < std::tie(rhs.package, rhs.type, rhs.entry);
+}
+
+inline bool ResourceNameRef::operator==(const ResourceNameRef& rhs) const {
+ return std::tie(package, type, entry)
+ == std::tie(rhs.package, rhs.type, rhs.entry);
+}
+
+inline bool ResourceNameRef::operator!=(const ResourceNameRef& rhs) const {
+ return std::tie(package, type, 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;
+}
+
+} // namespace aapt
+
+#endif // AAPT_RESOURCE_H
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
new file mode 100644
index 0000000..13f916b
--- /dev/null
+++ b/tools/aapt2/ResourceParser.cpp
@@ -0,0 +1,1401 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Logger.h"
+#include "ResourceParser.h"
+#include "ResourceValues.h"
+#include "ScopedXmlPullParser.h"
+#include "SourceXmlPullParser.h"
+#include "Util.h"
+#include "XliffXmlPullParser.h"
+
+#include <sstream>
+
+namespace aapt {
+
+void ResourceParser::extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
+ StringPiece16* outType, StringPiece16* outEntry) {
+ const char16_t* start = str.data();
+ const char16_t* end = start + str.size();
+ const char16_t* current = start;
+ while (current != end) {
+ if (outType->size() == 0 && *current == u'/') {
+ outType->assign(start, current - start);
+ start = current + 1;
+ } else if (outPackage->size() == 0 && *current == u':') {
+ outPackage->assign(start, current - start);
+ start = current + 1;
+ }
+ current++;
+ }
+ outEntry->assign(start, end - start);
+}
+
+bool ResourceParser::tryParseReference(const StringPiece16& str, ResourceNameRef* outRef,
+ bool* outCreate, bool* outPrivate) {
+ StringPiece16 trimmedStr(util::trimWhitespace(str));
+ if (trimmedStr.empty()) {
+ return false;
+ }
+
+ if (trimmedStr.data()[0] == u'@') {
+ size_t offset = 1;
+ *outCreate = false;
+ if (trimmedStr.data()[1] == u'+') {
+ *outCreate = true;
+ offset += 1;
+ } else if (trimmedStr.data()[1] == u'*') {
+ *outPrivate = true;
+ offset += 1;
+ }
+ StringPiece16 package;
+ StringPiece16 type;
+ StringPiece16 entry;
+ extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset),
+ &package, &type, &entry);
+
+ const ResourceType* parsedType = parseResourceType(type);
+ if (!parsedType) {
+ return false;
+ }
+
+ if (*outCreate && *parsedType != ResourceType::kId) {
+ return false;
+ }
+
+ outRef->package = package;
+ outRef->type = *parsedType;
+ outRef->entry = entry;
+ return true;
+ }
+ return false;
+}
+
+bool ResourceParser::tryParseAttributeReference(const StringPiece16& str,
+ ResourceNameRef* outRef) {
+ StringPiece16 trimmedStr(util::trimWhitespace(str));
+ if (trimmedStr.empty()) {
+ return false;
+ }
+
+ if (*trimmedStr.data() == u'?') {
+ StringPiece16 package;
+ StringPiece16 type;
+ StringPiece16 entry;
+ extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), &package, &type, &entry);
+
+ if (!type.empty() && type != u"attr") {
+ return false;
+ }
+
+ outRef->package = package;
+ outRef->type = ResourceType::kAttr;
+ outRef->entry = entry;
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Style parent's are a bit different. We accept the following formats:
+ *
+ * @[package:]style/<entry>
+ * ?[package:]style/<entry>
+ * <package>:[style/]<entry>
+ * [package:style/]<entry>
+ */
+bool ResourceParser::parseStyleParentReference(const StringPiece16& str, Reference* outReference,
+ std::string* outError) {
+ if (str.empty()) {
+ return true;
+ }
+
+ StringPiece16 name = str;
+
+ bool hasLeadingIdentifiers = false;
+ bool privateRef = false;
+
+ // Skip over these identifiers. A style's parent is a normal reference.
+ if (name.data()[0] == u'@' || name.data()[0] == u'?') {
+ hasLeadingIdentifiers = true;
+ name = name.substr(1, name.size() - 1);
+ if (name.data()[0] == u'*') {
+ privateRef = true;
+ name = name.substr(1, name.size() - 1);
+ }
+ }
+
+ ResourceNameRef ref;
+ ref.type = ResourceType::kStyle;
+
+ StringPiece16 typeStr;
+ extractResourceName(name, &ref.package, &typeStr, &ref.entry);
+ if (!typeStr.empty()) {
+ // If we have a type, make sure it is a Style.
+ const ResourceType* parsedType = parseResourceType(typeStr);
+ if (!parsedType || *parsedType != ResourceType::kStyle) {
+ std::stringstream err;
+ err << "invalid resource type '" << typeStr << "' for parent of style";
+ *outError = err.str();
+ return false;
+ }
+ } else {
+ // No type was defined, this should not have a leading identifier.
+ if (hasLeadingIdentifiers) {
+ std::stringstream err;
+ err << "invalid parent reference '" << str << "'";
+ *outError = err.str();
+ return false;
+ }
+ }
+
+ if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) {
+ std::stringstream err;
+ err << "invalid parent reference '" << str << "'";
+ *outError = err.str();
+ return false;
+ }
+
+ outReference->name = ref.toResourceName();
+ outReference->privateReference = privateRef;
+ return true;
+}
+
+std::unique_ptr<Reference> ResourceParser::tryParseReference(const StringPiece16& str,
+ bool* outCreate) {
+ ResourceNameRef ref;
+ bool privateRef = false;
+ if (tryParseReference(str, &ref, outCreate, &privateRef)) {
+ std::unique_ptr<Reference> value = util::make_unique<Reference>(ref);
+ value->privateReference = privateRef;
+ return value;
+ }
+
+ if (tryParseAttributeReference(str, &ref)) {
+ *outCreate = false;
+ return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
+ }
+ return {};
+}
+
+std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseNullOrEmpty(const StringPiece16& str) {
+ StringPiece16 trimmedStr(util::trimWhitespace(str));
+ android::Res_value value = {};
+ if (trimmedStr == u"@null") {
+ // TYPE_NULL with data set to 0 is interpreted by the runtime as an error.
+ // Instead we set the data type to TYPE_REFERENCE with a value of 0.
+ value.dataType = android::Res_value::TYPE_REFERENCE;
+ } else if (trimmedStr == u"@empty") {
+ // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime.
+ value.dataType = android::Res_value::TYPE_NULL;
+ value.data = android::Res_value::DATA_NULL_EMPTY;
+ } else {
+ return {};
+ }
+ return util::make_unique<BinaryPrimitive>(value);
+}
+
+std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseEnumSymbol(const Attribute& enumAttr,
+ const StringPiece16& str) {
+ StringPiece16 trimmedStr(util::trimWhitespace(str));
+ for (const auto& entry : enumAttr.symbols) {
+ // Enum symbols are stored as @package:id/symbol resources,
+ // so we need to match against the 'entry' part of the identifier.
+ const ResourceName& enumSymbolResourceName = entry.symbol.name;
+ if (trimmedStr == enumSymbolResourceName.entry) {
+ android::Res_value value = {};
+ value.dataType = android::Res_value::TYPE_INT_DEC;
+ value.data = entry.value;
+ return util::make_unique<BinaryPrimitive>(value);
+ }
+ }
+ return {};
+}
+
+std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseFlagSymbol(const Attribute& flagAttr,
+ const StringPiece16& str) {
+ android::Res_value flags = {};
+ flags.dataType = android::Res_value::TYPE_INT_DEC;
+
+ for (StringPiece16 part : util::tokenize(str, u'|')) {
+ StringPiece16 trimmedPart = util::trimWhitespace(part);
+
+ bool flagSet = false;
+ for (const auto& entry : flagAttr.symbols) {
+ // Flag symbols are stored as @package:id/symbol resources,
+ // so we need to match against the 'entry' part of the identifier.
+ const ResourceName& flagSymbolResourceName = entry.symbol.name;
+ if (trimmedPart == flagSymbolResourceName.entry) {
+ flags.data |= entry.value;
+ flagSet = true;
+ break;
+ }
+ }
+
+ if (!flagSet) {
+ return {};
+ }
+ }
+ return util::make_unique<BinaryPrimitive>(flags);
+}
+
+static uint32_t parseHex(char16_t c, bool* outError) {
+ if (c >= u'0' && c <= u'9') {
+ return c - u'0';
+ } else if (c >= u'a' && c <= u'f') {
+ return c - u'a' + 0xa;
+ } else if (c >= u'A' && c <= u'F') {
+ return c - u'A' + 0xa;
+ } else {
+ *outError = true;
+ return 0xffffffffu;
+ }
+}
+
+std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseColor(const StringPiece16& str) {
+ StringPiece16 colorStr(util::trimWhitespace(str));
+ const char16_t* start = colorStr.data();
+ const size_t len = colorStr.size();
+ if (len == 0 || start[0] != u'#') {
+ return {};
+ }
+
+ android::Res_value value = {};
+ bool error = false;
+ if (len == 4) {
+ value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4;
+ value.data = 0xff000000u;
+ value.data |= parseHex(start[1], &error) << 20;
+ value.data |= parseHex(start[1], &error) << 16;
+ value.data |= parseHex(start[2], &error) << 12;
+ value.data |= parseHex(start[2], &error) << 8;
+ value.data |= parseHex(start[3], &error) << 4;
+ value.data |= parseHex(start[3], &error);
+ } else if (len == 5) {
+ value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4;
+ value.data |= parseHex(start[1], &error) << 28;
+ value.data |= parseHex(start[1], &error) << 24;
+ value.data |= parseHex(start[2], &error) << 20;
+ value.data |= parseHex(start[2], &error) << 16;
+ value.data |= parseHex(start[3], &error) << 12;
+ value.data |= parseHex(start[3], &error) << 8;
+ value.data |= parseHex(start[4], &error) << 4;
+ value.data |= parseHex(start[4], &error);
+ } else if (len == 7) {
+ value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
+ value.data = 0xff000000u;
+ value.data |= parseHex(start[1], &error) << 20;
+ value.data |= parseHex(start[2], &error) << 16;
+ value.data |= parseHex(start[3], &error) << 12;
+ value.data |= parseHex(start[4], &error) << 8;
+ value.data |= parseHex(start[5], &error) << 4;
+ value.data |= parseHex(start[6], &error);
+ } else if (len == 9) {
+ value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8;
+ value.data |= parseHex(start[1], &error) << 28;
+ value.data |= parseHex(start[2], &error) << 24;
+ value.data |= parseHex(start[3], &error) << 20;
+ value.data |= parseHex(start[4], &error) << 16;
+ value.data |= parseHex(start[5], &error) << 12;
+ value.data |= parseHex(start[6], &error) << 8;
+ value.data |= parseHex(start[7], &error) << 4;
+ value.data |= parseHex(start[8], &error);
+ } else {
+ return {};
+ }
+ return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value);
+}
+
+std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseBool(const StringPiece16& str) {
+ StringPiece16 trimmedStr(util::trimWhitespace(str));
+ uint32_t data = 0;
+ if (trimmedStr == u"true" || trimmedStr == u"TRUE") {
+ data = 0xffffffffu;
+ } else if (trimmedStr != u"false" && trimmedStr != u"FALSE") {
+ return {};
+ }
+ android::Res_value value = {};
+ value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
+ value.data = data;
+ return util::make_unique<BinaryPrimitive>(value);
+}
+
+std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseInt(const StringPiece16& str) {
+ android::Res_value value;
+ if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) {
+ return {};
+ }
+ return util::make_unique<BinaryPrimitive>(value);
+}
+
+std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseFloat(const StringPiece16& str) {
+ android::Res_value value;
+ if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) {
+ return {};
+ }
+ return util::make_unique<BinaryPrimitive>(value);
+}
+
+uint32_t ResourceParser::androidTypeToAttributeTypeMask(uint16_t type) {
+ switch (type) {
+ case android::Res_value::TYPE_NULL:
+ case android::Res_value::TYPE_REFERENCE:
+ case android::Res_value::TYPE_ATTRIBUTE:
+ case android::Res_value::TYPE_DYNAMIC_REFERENCE:
+ return android::ResTable_map::TYPE_REFERENCE;
+
+ case android::Res_value::TYPE_STRING:
+ return android::ResTable_map::TYPE_STRING;
+
+ case android::Res_value::TYPE_FLOAT:
+ return android::ResTable_map::TYPE_FLOAT;
+
+ case android::Res_value::TYPE_DIMENSION:
+ return android::ResTable_map::TYPE_DIMENSION;
+
+ case android::Res_value::TYPE_FRACTION:
+ return android::ResTable_map::TYPE_FRACTION;
+
+ case android::Res_value::TYPE_INT_DEC:
+ case android::Res_value::TYPE_INT_HEX:
+ return android::ResTable_map::TYPE_INTEGER |
+ android::ResTable_map::TYPE_ENUM |
+ android::ResTable_map::TYPE_FLAGS;
+
+ case android::Res_value::TYPE_INT_BOOLEAN:
+ return android::ResTable_map::TYPE_BOOLEAN;
+
+ case android::Res_value::TYPE_INT_COLOR_ARGB8:
+ case android::Res_value::TYPE_INT_COLOR_RGB8:
+ case android::Res_value::TYPE_INT_COLOR_ARGB4:
+ case android::Res_value::TYPE_INT_COLOR_RGB4:
+ return android::ResTable_map::TYPE_COLOR;
+
+ default:
+ return 0;
+ };
+}
+
+std::unique_ptr<Item> ResourceParser::parseItemForAttribute(
+ const StringPiece16& value, uint32_t typeMask,
+ std::function<void(const ResourceName&)> onCreateReference) {
+ std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value);
+ if (nullOrEmpty) {
+ return std::move(nullOrEmpty);
+ }
+
+ bool create = false;
+ std::unique_ptr<Reference> reference = tryParseReference(value, &create);
+ if (reference) {
+ if (create && onCreateReference) {
+ onCreateReference(reference->name);
+ }
+ return std::move(reference);
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_COLOR) {
+ // Try parsing this as a color.
+ std::unique_ptr<BinaryPrimitive> color = tryParseColor(value);
+ if (color) {
+ return std::move(color);
+ }
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
+ // Try parsing this as a boolean.
+ std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value);
+ if (boolean) {
+ return std::move(boolean);
+ }
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_INTEGER) {
+ // Try parsing this as an integer.
+ std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value);
+ if (integer) {
+ return std::move(integer);
+ }
+ }
+
+ const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT |
+ android::ResTable_map::TYPE_DIMENSION |
+ android::ResTable_map::TYPE_FRACTION;
+ if (typeMask & floatMask) {
+ // Try parsing this as a float.
+ std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value);
+ if (floatingPoint) {
+ if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) {
+ return std::move(floatingPoint);
+ }
+ }
+ }
+ return {};
+}
+
+/**
+ * We successively try to parse the string as a resource type that the Attribute
+ * allows.
+ */
+std::unique_ptr<Item> ResourceParser::parseItemForAttribute(
+ const StringPiece16& str, const Attribute& attr,
+ std::function<void(const ResourceName&)> onCreateReference) {
+ const uint32_t typeMask = attr.typeMask;
+ std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, onCreateReference);
+ if (value) {
+ return value;
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_ENUM) {
+ // Try parsing this as an enum.
+ std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str);
+ if (enumValue) {
+ return std::move(enumValue);
+ }
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_FLAGS) {
+ // Try parsing this as a flag.
+ std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str);
+ if (flagValue) {
+ return std::move(flagValue);
+ }
+ }
+ return {};
+}
+
+ResourceParser::ResourceParser(const std::shared_ptr<ResourceTable>& table, const Source& source,
+ const ConfigDescription& config,
+ const std::shared_ptr<XmlPullParser>& parser) :
+ mTable(table), mSource(source), mConfig(config), mLogger(source),
+ mParser(std::make_shared<XliffXmlPullParser>(parser)) {
+}
+
+/**
+ * Build a string from XML that converts nested elements into Span objects.
+ */
+bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,
+ StyleString* outStyleString) {
+ std::vector<Span> spanStack;
+
+ outRawString->clear();
+ outStyleString->spans.clear();
+ util::StringBuilder builder;
+ size_t depth = 1;
+ while (XmlPullParser::isGoodEvent(parser->next())) {
+ const XmlPullParser::Event event = parser->getEvent();
+ if (event == XmlPullParser::Event::kEndElement) {
+ depth--;
+ if (depth == 0) {
+ break;
+ }
+
+ spanStack.back().lastChar = builder.str().size();
+ outStyleString->spans.push_back(spanStack.back());
+ spanStack.pop_back();
+
+ } else if (event == XmlPullParser::Event::kText) {
+ // TODO(adamlesinski): Verify format strings.
+ outRawString->append(parser->getText());
+ builder.append(parser->getText());
+
+ } else if (event == XmlPullParser::Event::kStartElement) {
+ if (parser->getElementNamespace().size() > 0) {
+ mLogger.warn(parser->getLineNumber())
+ << "skipping element '"
+ << parser->getElementName()
+ << "' with unknown namespace '"
+ << parser->getElementNamespace()
+ << "'."
+ << std::endl;
+ XmlPullParser::skipCurrentElement(parser);
+ 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()) {
+ mLogger.error(parser->getLineNumber())
+ << "style string '"
+ << builder.str()
+ << "' is too long."
+ << std::endl;
+ return false;
+ }
+ spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) });
+
+ } else if (event == XmlPullParser::Event::kComment) {
+ // Skip
+ } else {
+ mLogger.warn(parser->getLineNumber())
+ << "unknown event "
+ << event
+ << "."
+ << std::endl;
+ }
+ }
+ assert(spanStack.empty() && "spans haven't been fully processed");
+
+ outStyleString->str = builder.str();
+ return true;
+}
+
+bool ResourceParser::parse() {
+ while (XmlPullParser::isGoodEvent(mParser->next())) {
+ if (mParser->getEvent() != XmlPullParser::Event::kStartElement) {
+ continue;
+ }
+
+ ScopedXmlPullParser parser(mParser.get());
+ if (!parser.getElementNamespace().empty() ||
+ parser.getElementName() != u"resources") {
+ mLogger.error(parser.getLineNumber())
+ << "root element must be <resources> in the global namespace."
+ << std::endl;
+ return false;
+ }
+
+ if (!parseResources(&parser)) {
+ return false;
+ }
+ }
+
+ if (mParser->getEvent() == XmlPullParser::Event::kBadDocument) {
+ mLogger.error(mParser->getLineNumber())
+ << mParser->getLastError()
+ << std::endl;
+ return false;
+ }
+ return true;
+}
+
+bool ResourceParser::parseResources(XmlPullParser* parser) {
+ bool success = true;
+
+ std::u16string comment;
+ while (XmlPullParser::isGoodEvent(parser->next())) {
+ const XmlPullParser::Event event = parser->getEvent();
+ if (event == XmlPullParser::Event::kComment) {
+ comment = parser->getComment();
+ continue;
+ }
+
+ if (event == XmlPullParser::Event::kText) {
+ if (!util::trimWhitespace(parser->getText()).empty()) {
+ comment = u"";
+ }
+ continue;
+ }
+
+ if (event != XmlPullParser::Event::kStartElement) {
+ continue;
+ }
+
+ ScopedXmlPullParser childParser(parser);
+
+ if (!childParser.getElementNamespace().empty()) {
+ // Skip unknown namespace.
+ continue;
+ }
+
+ StringPiece16 name = childParser.getElementName();
+ if (name == u"skip" || name == u"eat-comment") {
+ continue;
+ }
+
+ if (name == u"private-symbols") {
+ // Handle differently.
+ mLogger.note(childParser.getLineNumber())
+ << "got a <private-symbols> tag."
+ << std::endl;
+ continue;
+ }
+
+ const auto endAttrIter = childParser.endAttributes();
+ auto attrIter = childParser.findAttribute(u"", u"name");
+ if (attrIter == endAttrIter || attrIter->value.empty()) {
+ mLogger.error(childParser.getLineNumber())
+ << "<" << name << "> tag must have a 'name' attribute."
+ << std::endl;
+ success = false;
+ continue;
+ }
+
+ // Copy because our iterator will go out of scope when
+ // we parse more XML.
+ std::u16string attributeName = attrIter->value;
+
+ if (name == u"item") {
+ // Items simply have their type encoded in the type attribute.
+ auto typeIter = childParser.findAttribute(u"", u"type");
+ if (typeIter == endAttrIter || typeIter->value.empty()) {
+ mLogger.error(childParser.getLineNumber())
+ << "<item> must have a 'type' attribute."
+ << std::endl;
+ success = false;
+ continue;
+ }
+ name = typeIter->value;
+ }
+
+ if (name == u"id") {
+ success &= mTable->addResource(ResourceNameRef{ {}, ResourceType::kId, attributeName },
+ {}, mSource.line(childParser.getLineNumber()),
+ util::make_unique<Id>());
+ } else if (name == u"string") {
+ success &= parseString(&childParser,
+ ResourceNameRef{ {}, ResourceType::kString, attributeName });
+ } else if (name == u"color") {
+ success &= parseColor(&childParser,
+ ResourceNameRef{ {}, ResourceType::kColor, attributeName });
+ } else if (name == u"drawable") {
+ success &= parseColor(&childParser,
+ ResourceNameRef{ {}, ResourceType::kDrawable, attributeName });
+ } else if (name == u"bool") {
+ success &= parsePrimitive(&childParser,
+ ResourceNameRef{ {}, ResourceType::kBool, attributeName });
+ } else if (name == u"integer") {
+ success &= parsePrimitive(
+ &childParser,
+ ResourceNameRef{ {}, ResourceType::kInteger, attributeName });
+ } else if (name == u"dimen") {
+ success &= parsePrimitive(&childParser,
+ ResourceNameRef{ {}, ResourceType::kDimen, attributeName });
+ } else if (name == u"fraction") {
+// success &= parsePrimitive(
+// &childParser,
+// ResourceNameRef{ {}, ResourceType::kFraction, attributeName });
+ } else if (name == u"style") {
+ success &= parseStyle(&childParser,
+ ResourceNameRef{ {}, ResourceType::kStyle, attributeName });
+ } else if (name == u"plurals") {
+ success &= parsePlural(&childParser,
+ ResourceNameRef{ {}, ResourceType::kPlurals, attributeName });
+ } else if (name == u"array") {
+ success &= parseArray(&childParser,
+ ResourceNameRef{ {}, ResourceType::kArray, attributeName },
+ android::ResTable_map::TYPE_ANY);
+ } else if (name == u"string-array") {
+ success &= parseArray(&childParser,
+ ResourceNameRef{ {}, ResourceType::kArray, attributeName },
+ android::ResTable_map::TYPE_STRING);
+ } else if (name == u"integer-array") {
+ success &= parseArray(&childParser,
+ ResourceNameRef{ {}, ResourceType::kArray, attributeName },
+ android::ResTable_map::TYPE_INTEGER);
+ } else if (name == u"public") {
+ success &= parsePublic(&childParser, attributeName);
+ } else if (name == u"declare-styleable") {
+ success &= parseDeclareStyleable(
+ &childParser,
+ ResourceNameRef{ {}, ResourceType::kStyleable, attributeName });
+ } else if (name == u"attr") {
+ success &= parseAttr(&childParser,
+ ResourceNameRef{ {}, ResourceType::kAttr, attributeName });
+ } else if (name == u"bag") {
+ } else if (name == u"public-padding") {
+ } else if (name == u"java-symbol") {
+ } else if (name == u"add-resource") {
+ }
+ }
+
+ if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
+ mLogger.error(parser->getLineNumber())
+ << parser->getLastError()
+ << std::endl;
+ return false;
+ }
+ return success;
+}
+
+
+
+enum {
+ kAllowRawString = true,
+ kNoRawString = false
+};
+
+/**
+ * Reads the entire XML subtree and attempts to parse it as some Item,
+ * with typeMask denoting which items it can be. If allowRawValue is
+ * true, a RawString is returned if the XML couldn't be parsed as
+ * an Item. If allowRawValue is false, nullptr is returned in this
+ * case.
+ */
+std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, uint32_t typeMask,
+ bool allowRawValue) {
+ const size_t beginXmlLine = parser->getLineNumber();
+
+ std::u16string rawValue;
+ StyleString styleString;
+ if (!flattenXmlSubtree(parser, &rawValue, &styleString)) {
+ return {};
+ }
+
+ StringPool& pool = mTable->getValueStringPool();
+
+ if (!styleString.spans.empty()) {
+ // This can only be a StyledString.
+ return util::make_unique<StyledString>(
+ pool.makeRef(styleString, StringPool::Context{ 1, mConfig }));
+ }
+
+ auto onCreateReference = [&](const ResourceName& name) {
+ // name.package can be empty here, as it will assume the package name of the table.
+ mTable->addResource(name, {}, mSource.line(beginXmlLine), util::make_unique<Id>());
+ };
+
+ // Process the raw value.
+ std::unique_ptr<Item> processedItem = parseItemForAttribute(rawValue, typeMask,
+ onCreateReference);
+ if (processedItem) {
+ // Fix up the reference.
+ visitFunc<Reference>(*processedItem, [&](Reference& ref) {
+ if (!ref.name.package.empty()) {
+ // The package name was set, so lookup its alias.
+ parser->applyPackageAlias(&ref.name.package, mTable->getPackage());
+ } else {
+ // The package name was left empty, so it assumes the default package
+ // without alias lookup.
+ ref.name.package = mTable->getPackage();
+ }
+ });
+ return processedItem;
+ }
+
+ // Try making a regular string.
+ if (typeMask & android::ResTable_map::TYPE_STRING) {
+ // Use the trimmed, escaped string.
+ return util::make_unique<String>(
+ pool.makeRef(styleString.str, StringPool::Context{ 1, mConfig }));
+ }
+
+ // We can't parse this so return a RawString if we are allowed.
+ if (allowRawValue) {
+ return util::make_unique<RawString>(
+ pool.makeRef(rawValue, StringPool::Context{ 1, mConfig }));
+ }
+ return {};
+}
+
+bool ResourceParser::parseString(XmlPullParser* parser, const ResourceNameRef& resourceName) {
+ const SourceLine source = mSource.line(parser->getLineNumber());
+
+ // Mark the string as untranslateable if needed.
+ const auto endAttrIter = parser->endAttributes();
+ auto attrIter = parser->findAttribute(u"", u"untranslateable");
+ // bool untranslateable = attrIter != endAttrIter;
+ // TODO(adamlesinski): Do something with this (mark the string).
+
+ // Deal with the product.
+ attrIter = parser->findAttribute(u"", u"product");
+ if (attrIter != endAttrIter) {
+ if (attrIter->value != u"default" && attrIter->value != u"phone") {
+ // TODO(adamlesinski): Match products.
+ return true;
+ }
+ }
+
+ std::unique_ptr<Item> processedItem = parseXml(parser, android::ResTable_map::TYPE_STRING,
+ kNoRawString);
+ if (!processedItem) {
+ mLogger.error(source.line)
+ << "not a valid string."
+ << std::endl;
+ return false;
+ }
+
+ return mTable->addResource(resourceName, mConfig, source, std::move(processedItem));
+}
+
+bool ResourceParser::parseColor(XmlPullParser* parser, const ResourceNameRef& resourceName) {
+ const SourceLine source = mSource.line(parser->getLineNumber());
+
+ std::unique_ptr<Item> item = parseXml(parser, android::ResTable_map::TYPE_COLOR, kNoRawString);
+ if (!item) {
+ mLogger.error(source.line) << "invalid color." << std::endl;
+ return false;
+ }
+ return mTable->addResource(resourceName, mConfig, source, std::move(item));
+}
+
+bool ResourceParser::parsePrimitive(XmlPullParser* parser, const ResourceNameRef& resourceName) {
+ const SourceLine source = mSource.line(parser->getLineNumber());
+
+ uint32_t typeMask = 0;
+ switch (resourceName.type) {
+ case ResourceType::kInteger:
+ typeMask |= android::ResTable_map::TYPE_INTEGER;
+ break;
+
+ case ResourceType::kDimen:
+ typeMask |= android::ResTable_map::TYPE_DIMENSION
+ | android::ResTable_map::TYPE_FLOAT
+ | android::ResTable_map::TYPE_FRACTION;
+ break;
+
+ case ResourceType::kBool:
+ typeMask |= android::ResTable_map::TYPE_BOOLEAN;
+ break;
+
+ default:
+ assert(false);
+ break;
+ }
+
+ std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString);
+ if (!item) {
+ mLogger.error(source.line)
+ << "invalid "
+ << resourceName.type
+ << "."
+ << std::endl;
+ return false;
+ }
+
+ return mTable->addResource(resourceName, mConfig, source, std::move(item));
+}
+
+bool ResourceParser::parsePublic(XmlPullParser* parser, const StringPiece16& name) {
+ const SourceLine source = mSource.line(parser->getLineNumber());
+
+ const auto endAttrIter = parser->endAttributes();
+ const auto typeAttrIter = parser->findAttribute(u"", u"type");
+ if (typeAttrIter == endAttrIter || typeAttrIter->value.empty()) {
+ mLogger.error(source.line)
+ << "<public> must have a 'type' attribute."
+ << std::endl;
+ return false;
+ }
+
+ const ResourceType* parsedType = parseResourceType(typeAttrIter->value);
+ if (!parsedType) {
+ mLogger.error(source.line)
+ << "invalid resource type '"
+ << typeAttrIter->value
+ << "' in <public>."
+ << std::endl;
+ return false;
+ }
+
+ ResourceNameRef resourceName { {}, *parsedType, name };
+ ResourceId resourceId;
+
+ const auto idAttrIter = parser->findAttribute(u"", u"id");
+ if (idAttrIter != endAttrIter && !idAttrIter->value.empty()) {
+ android::Res_value val;
+ bool result = android::ResTable::stringToInt(idAttrIter->value.data(),
+ idAttrIter->value.size(), &val);
+ resourceId.id = val.data;
+ if (!result || !resourceId.isValid()) {
+ mLogger.error(source.line)
+ << "invalid resource ID '"
+ << idAttrIter->value
+ << "' in <public>."
+ << std::endl;
+ return false;
+ }
+ }
+
+ if (*parsedType == ResourceType::kId) {
+ // An ID marked as public is also the definition of an ID.
+ mTable->addResource(resourceName, {}, source, util::make_unique<Id>());
+ }
+
+ return mTable->markPublic(resourceName, resourceId, source);
+}
+
+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 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;
+}
+
+bool ResourceParser::parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName) {
+ const SourceLine source = mSource.line(parser->getLineNumber());
+ ResourceName actualName = resourceName.toResourceName();
+ std::unique_ptr<Attribute> attr = parseAttrImpl(parser, &actualName, false);
+ if (!attr) {
+ return false;
+ }
+ return mTable->addResource(actualName, mConfig, source, std::move(attr));
+}
+
+std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser,
+ ResourceName* resourceName,
+ bool weak) {
+ uint32_t typeMask = 0;
+
+ const auto endAttrIter = parser->endAttributes();
+ const auto formatAttrIter = parser->findAttribute(u"", u"format");
+ if (formatAttrIter != endAttrIter) {
+ typeMask = parseFormatAttribute(formatAttrIter->value);
+ if (typeMask == 0) {
+ mLogger.error(parser->getLineNumber())
+ << "invalid attribute format '"
+ << formatAttrIter->value
+ << "'."
+ << std::endl;
+ return {};
+ }
+ }
+
+ // If this is a declaration, the package name may be in the name. Separate these out.
+ // Eg. <attr name="android:text" />
+ // No format attribute is allowed.
+ if (weak && formatAttrIter == endAttrIter) {
+ StringPiece16 package, type, name;
+ extractResourceName(resourceName->entry, &package, &type, &name);
+ if (type.empty() && !package.empty()) {
+ resourceName->package = package.toString();
+ resourceName->entry = name.toString();
+ }
+ }
+
+ std::vector<Attribute::Symbol> items;
+
+ bool error = false;
+ while (XmlPullParser::isGoodEvent(parser->next())) {
+ if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ continue;
+ }
+
+ ScopedXmlPullParser childParser(parser);
+
+ const std::u16string& name = childParser.getElementName();
+ if (!childParser.getElementNamespace().empty()
+ || (name != u"flag" && name != u"enum")) {
+ mLogger.error(childParser.getLineNumber())
+ << "unexpected tag <"
+ << name
+ << "> in <attr>."
+ << std::endl;
+ error = true;
+ continue;
+ }
+
+ if (name == u"enum") {
+ if (typeMask & android::ResTable_map::TYPE_FLAGS) {
+ mLogger.error(childParser.getLineNumber())
+ << "can not define an <enum>; already defined a <flag>."
+ << std::endl;
+ error = true;
+ continue;
+ }
+ typeMask |= android::ResTable_map::TYPE_ENUM;
+ } else if (name == u"flag") {
+ if (typeMask & android::ResTable_map::TYPE_ENUM) {
+ mLogger.error(childParser.getLineNumber())
+ << "can not define a <flag>; already defined an <enum>."
+ << std::endl;
+ error = true;
+ continue;
+ }
+ typeMask |= android::ResTable_map::TYPE_FLAGS;
+ }
+
+ Attribute::Symbol item;
+ if (parseEnumOrFlagItem(&childParser, name, &item)) {
+ if (!mTable->addResource(item.symbol.name, mConfig,
+ mSource.line(childParser.getLineNumber()),
+ util::make_unique<Id>())) {
+ error = true;
+ } else {
+ items.push_back(std::move(item));
+ }
+ } else {
+ error = true;
+ }
+ }
+
+ if (error) {
+ return {};
+ }
+
+ std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
+ attr->symbols.swap(items);
+ attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY);
+ return attr;
+}
+
+bool ResourceParser::parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag,
+ Attribute::Symbol* outSymbol) {
+ const auto attrIterEnd = parser->endAttributes();
+ const auto nameAttrIter = parser->findAttribute(u"", u"name");
+ if (nameAttrIter == attrIterEnd || nameAttrIter->value.empty()) {
+ mLogger.error(parser->getLineNumber())
+ << "no attribute 'name' found for tag <" << tag << ">."
+ << std::endl;
+ return false;
+ }
+
+ const auto valueAttrIter = parser->findAttribute(u"", u"value");
+ if (valueAttrIter == attrIterEnd || valueAttrIter->value.empty()) {
+ mLogger.error(parser->getLineNumber())
+ << "no attribute 'value' found for tag <" << tag << ">."
+ << std::endl;
+ return false;
+ }
+
+ android::Res_value val;
+ if (!android::ResTable::stringToInt(valueAttrIter->value.data(),
+ valueAttrIter->value.size(), &val)) {
+ mLogger.error(parser->getLineNumber())
+ << "invalid value '"
+ << valueAttrIter->value
+ << "' for <" << tag << ">; must be an integer."
+ << std::endl;
+ return false;
+ }
+
+ outSymbol->symbol.name = ResourceName {
+ mTable->getPackage(), ResourceType::kId, nameAttrIter->value };
+ outSymbol->value = val.data;
+ return true;
+}
+
+static bool parseXmlAttributeName(StringPiece16 str, ResourceName* outName) {
+ str = util::trimWhitespace(str);
+ const char16_t* const start = str.data();
+ const char16_t* const end = start + str.size();
+ const char16_t* p = start;
+
+ StringPiece16 package;
+ StringPiece16 name;
+ while (p != end) {
+ if (*p == u':') {
+ package = StringPiece16(start, p - start);
+ name = StringPiece16(p + 1, end - (p + 1));
+ break;
+ }
+ p++;
+ }
+
+ outName->package = package.toString();
+ outName->type = ResourceType::kAttr;
+ if (name.size() == 0) {
+ outName->entry = str.toString();
+ } else {
+ outName->entry = name.toString();
+ }
+ return true;
+}
+
+bool ResourceParser::parseUntypedItem(XmlPullParser* parser, Style& style) {
+ const auto endAttrIter = parser->endAttributes();
+ const auto nameAttrIter = parser->findAttribute(u"", u"name");
+ if (nameAttrIter == endAttrIter || nameAttrIter->value.empty()) {
+ mLogger.error(parser->getLineNumber())
+ << "<item> must have a 'name' attribute."
+ << std::endl;
+ return false;
+ }
+
+ ResourceName key;
+ if (!parseXmlAttributeName(nameAttrIter->value, &key)) {
+ mLogger.error(parser->getLineNumber())
+ << "invalid attribute name '"
+ << nameAttrIter->value
+ << "'."
+ << std::endl;
+ return false;
+ }
+
+ if (!key.package.empty()) {
+ // We have a package name set, so lookup its alias.
+ parser->applyPackageAlias(&key.package, mTable->getPackage());
+ } else {
+ // The package name was omitted, so use the default package name with
+ // no alias lookup.
+ key.package = mTable->getPackage();
+ }
+
+ std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString);
+ if (!value) {
+ return false;
+ }
+
+ style.entries.push_back(Style::Entry{ Reference(key), std::move(value) });
+ return true;
+}
+
+bool ResourceParser::parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName) {
+ const SourceLine source = mSource.line(parser->getLineNumber());
+ std::unique_ptr<Style> style = util::make_unique<Style>();
+
+ const auto endAttrIter = parser->endAttributes();
+ const auto parentAttrIter = parser->findAttribute(u"", u"parent");
+ if (parentAttrIter != endAttrIter) {
+ std::string errStr;
+ if (!parseStyleParentReference(parentAttrIter->value, &style->parent, &errStr)) {
+ mLogger.error(source.line) << errStr << "." << std::endl;
+ return false;
+ }
+
+ if (!style->parent.name.package.empty()) {
+ // Try to interpret the package name as an alias. These take precedence.
+ parser->applyPackageAlias(&style->parent.name.package, mTable->getPackage());
+ } else {
+ // If no package is specified, this can not be an alias and is the local package.
+ style->parent.name.package = mTable->getPackage();
+ }
+ } else {
+ // No parent was specified, so try inferring it from the style name.
+ std::u16string styleName = resourceName.entry.toString();
+ size_t pos = styleName.find_last_of(u'.');
+ if (pos != std::string::npos) {
+ style->parentInferred = true;
+ style->parent.name.package = mTable->getPackage();
+ style->parent.name.type = ResourceType::kStyle;
+ style->parent.name.entry = styleName.substr(0, pos);
+ }
+ }
+
+ bool success = true;
+ while (XmlPullParser::isGoodEvent(parser->next())) {
+ if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ continue;
+ }
+
+ ScopedXmlPullParser childParser(parser);
+ const std::u16string& name = childParser.getElementName();
+ if (name == u"item") {
+ success &= parseUntypedItem(&childParser, *style);
+ } else {
+ mLogger.error(childParser.getLineNumber())
+ << "unexpected tag <"
+ << name
+ << "> in <style> resource."
+ << std::endl;
+ success = false;
+ }
+ }
+
+ if (!success) {
+ return false;
+ }
+
+ return mTable->addResource(resourceName, mConfig, source, std::move(style));
+}
+
+bool ResourceParser::parseArray(XmlPullParser* parser, const ResourceNameRef& resourceName,
+ uint32_t typeMask) {
+ const SourceLine source = mSource.line(parser->getLineNumber());
+ std::unique_ptr<Array> array = util::make_unique<Array>();
+
+ bool error = false;
+ while (XmlPullParser::isGoodEvent(parser->next())) {
+ if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ continue;
+ }
+
+ ScopedXmlPullParser childParser(parser);
+
+ if (childParser.getElementName() != u"item") {
+ mLogger.error(childParser.getLineNumber())
+ << "unexpected tag <"
+ << childParser.getElementName()
+ << "> in <array> resource."
+ << std::endl;
+ error = true;
+ continue;
+ }
+
+ std::unique_ptr<Item> item = parseXml(&childParser, typeMask, kNoRawString);
+ if (!item) {
+ error = true;
+ continue;
+ }
+ array->items.emplace_back(std::move(item));
+ }
+
+ if (error) {
+ return false;
+ }
+
+ return mTable->addResource(resourceName, mConfig, source, std::move(array));
+}
+
+bool ResourceParser::parsePlural(XmlPullParser* parser, const ResourceNameRef& resourceName) {
+ const SourceLine source = mSource.line(parser->getLineNumber());
+ std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+
+ bool success = true;
+ while (XmlPullParser::isGoodEvent(parser->next())) {
+ if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ continue;
+ }
+
+ ScopedXmlPullParser childParser(parser);
+
+ if (!childParser.getElementNamespace().empty() ||
+ childParser.getElementName() != u"item") {
+ success = false;
+ continue;
+ }
+
+ const auto endAttrIter = childParser.endAttributes();
+ auto attrIter = childParser.findAttribute(u"", u"quantity");
+ if (attrIter == endAttrIter || attrIter->value.empty()) {
+ mLogger.error(childParser.getLineNumber())
+ << "<item> in <plurals> requires attribute 'quantity'."
+ << std::endl;
+ success = false;
+ continue;
+ }
+
+ StringPiece16 trimmedQuantity = util::trimWhitespace(attrIter->value);
+ size_t index = 0;
+ if (trimmedQuantity == u"zero") {
+ index = Plural::Zero;
+ } else if (trimmedQuantity == u"one") {
+ index = Plural::One;
+ } else if (trimmedQuantity == u"two") {
+ index = Plural::Two;
+ } else if (trimmedQuantity == u"few") {
+ index = Plural::Few;
+ } else if (trimmedQuantity == u"many") {
+ index = Plural::Many;
+ } else if (trimmedQuantity == u"other") {
+ index = Plural::Other;
+ } else {
+ mLogger.error(childParser.getLineNumber())
+ << "<item> in <plural> has invalid value '"
+ << trimmedQuantity
+ << "' for attribute 'quantity'."
+ << std::endl;
+ success = false;
+ continue;
+ }
+
+ if (plural->values[index]) {
+ mLogger.error(childParser.getLineNumber())
+ << "duplicate quantity '"
+ << trimmedQuantity
+ << "'."
+ << std::endl;
+ success = false;
+ continue;
+ }
+
+ if (!(plural->values[index] = parseXml(&childParser, android::ResTable_map::TYPE_STRING,
+ kNoRawString))) {
+ success = false;
+ }
+ }
+
+ if (!success) {
+ return false;
+ }
+
+ return mTable->addResource(resourceName, mConfig, source, std::move(plural));
+}
+
+bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser,
+ const ResourceNameRef& resourceName) {
+ const SourceLine source = mSource.line(parser->getLineNumber());
+ std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
+
+ bool success = true;
+ while (XmlPullParser::isGoodEvent(parser->next())) {
+ if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ continue;
+ }
+
+ ScopedXmlPullParser childParser(parser);
+
+ const std::u16string& elementName = childParser.getElementName();
+ if (elementName == u"attr") {
+ const auto endAttrIter = childParser.endAttributes();
+ auto attrIter = childParser.findAttribute(u"", u"name");
+ if (attrIter == endAttrIter || attrIter->value.empty()) {
+ mLogger.error(childParser.getLineNumber())
+ << "<attr> tag must have a 'name' attribute."
+ << std::endl;
+ success = false;
+ continue;
+ }
+
+ // Copy because our iterator will be invalidated.
+ ResourceName attrResourceName = {
+ mTable->getPackage(),
+ ResourceType::kAttr,
+ attrIter->value
+ };
+
+ std::unique_ptr<Attribute> attr = parseAttrImpl(&childParser, &attrResourceName, true);
+ if (!attr) {
+ success = false;
+ continue;
+ }
+
+ styleable->entries.emplace_back(attrResourceName);
+
+ // The package may have been corrected to another package. If that is so,
+ // we don't add the declaration.
+ if (attrResourceName.package == mTable->getPackage()) {
+ success &= mTable->addResource(attrResourceName, mConfig,
+ mSource.line(childParser.getLineNumber()),
+ std::move(attr));
+ }
+
+ } else if (elementName != u"eat-comment" && elementName != u"skip") {
+ mLogger.error(childParser.getLineNumber())
+ << "<"
+ << elementName
+ << "> is not allowed inside <declare-styleable>."
+ << std::endl;
+ success = false;
+ }
+ }
+
+ if (!success) {
+ return false;
+ }
+
+ return mTable->addResource(resourceName, mConfig, source, std::move(styleable));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
new file mode 100644
index 0000000..7618999
--- /dev/null
+++ b/tools/aapt2/ResourceParser.h
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_RESOURCE_PARSER_H
+#define AAPT_RESOURCE_PARSER_H
+
+#include "ConfigDescription.h"
+#include "Logger.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "StringPiece.h"
+#include "StringPool.h"
+#include "XmlPullParser.h"
+
+#include <istream>
+#include <memory>
+
+namespace aapt {
+
+/*
+ * Parses an XML file for resources and adds them to a ResourceTable.
+ */
+class ResourceParser {
+public:
+ /*
+ * Extracts the package, type, and name from a string of the format:
+ *
+ * [package:]type/name
+ *
+ * where the package can be empty. Validation must be performed on each
+ * individual extracted piece to verify that the pieces are valid.
+ */
+ static void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
+ StringPiece16* outType, StringPiece16* outEntry);
+
+ /*
+ * Returns true if the string was parsed as a reference (@[+][package:]type/name), with
+ * `outReference` set to the parsed reference.
+ *
+ * If '+' was present in the reference, `outCreate` is set to true.
+ * If '*' was present in the reference, `outPrivate` is set to true.
+ */
+ static bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference,
+ bool* outCreate, bool* outPrivate);
+
+ /*
+ * Returns true if the string was parsed as an attribute reference (?[package:]type/name),
+ * with `outReference` set to the parsed reference.
+ */
+ static bool tryParseAttributeReference(const StringPiece16& str,
+ ResourceNameRef* outReference);
+
+ /*
+ * Returns true if the string `str` was parsed as a valid reference to a style.
+ * The format for a style parent is slightly more flexible than a normal reference:
+ *
+ * @[package:]style/<entry> or
+ * ?[package:]style/<entry> or
+ * <package>:[style/]<entry>
+ */
+ static bool parseStyleParentReference(const StringPiece16& str, Reference* outReference,
+ std::string* outError);
+
+ /*
+ * Returns a Reference object if the string was parsed as a resource or attribute reference,
+ * ( @[+][package:]type/name | ?[package:]type/name ) setting outCreate to true if
+ * the '+' was present in the string.
+ */
+ static std::unique_ptr<Reference> tryParseReference(const StringPiece16& str,
+ bool* outCreate);
+
+ /*
+ * Returns a BinaryPrimitve object representing @null or @empty if the string was parsed
+ * as one.
+ */
+ static std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str);
+
+ /*
+ * Returns a BinaryPrimitve object representing a color if the string was parsed
+ * as one.
+ */
+ static std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str);
+
+ /*
+ * Returns a BinaryPrimitve object representing a boolean if the string was parsed
+ * as one.
+ */
+ static std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str);
+
+ /*
+ * Returns a BinaryPrimitve object representing an integer if the string was parsed
+ * as one.
+ */
+ static std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str);
+
+ /*
+ * Returns a BinaryPrimitve object representing a floating point number
+ * (float, dimension, etc) if the string was parsed as one.
+ */
+ static std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str);
+
+ /*
+ * Returns a BinaryPrimitve object representing an enum symbol if the string was parsed
+ * as one.
+ */
+ static std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute& enumAttr,
+ const StringPiece16& str);
+
+ /*
+ * Returns a BinaryPrimitve object representing a flag symbol if the string was parsed
+ * as one.
+ */
+ static std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute& enumAttr,
+ const StringPiece16& str);
+ /*
+ * Try to convert a string to an Item for the given attribute. The attribute will
+ * restrict what values the string can be converted to.
+ * The callback function onCreateReference is called when the parsed item is a
+ * reference to an ID that must be created (@+id/foo).
+ */
+ static std::unique_ptr<Item> parseItemForAttribute(
+ const StringPiece16& value, const Attribute& attr,
+ std::function<void(const ResourceName&)> onCreateReference = {});
+
+ static std::unique_ptr<Item> parseItemForAttribute(
+ const StringPiece16& value, uint32_t typeMask,
+ std::function<void(const ResourceName&)> onCreateReference = {});
+
+ static uint32_t androidTypeToAttributeTypeMask(uint16_t type);
+
+ ResourceParser(const std::shared_ptr<ResourceTable>& table, const Source& source,
+ const ConfigDescription& config, const std::shared_ptr<XmlPullParser>& parser);
+
+ ResourceParser(const ResourceParser&) = delete; // No copy.
+
+ bool parse();
+
+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(XmlPullParser* parser, std::u16string* outRawString,\
+ StyleString* outStyleString);
+
+ /*
+ * Parses the XML subtree and converts it to an Item. The type of Item that can be
+ * parsed is denoted by the `typeMask`. If `allowRawValue` is true and the subtree
+ * can not be parsed as a regular Item, then a RawString is returned. Otherwise
+ * this returns nullptr.
+ */
+ std::unique_ptr<Item> parseXml(XmlPullParser* parser, uint32_t typeMask, bool allowRawValue);
+
+ bool parseResources(XmlPullParser* parser);
+ bool parseString(XmlPullParser* parser, const ResourceNameRef& resourceName);
+ bool parseColor(XmlPullParser* parser, const ResourceNameRef& resourceName);
+ bool parsePrimitive(XmlPullParser* parser, const ResourceNameRef& resourceName);
+ bool parsePublic(XmlPullParser* parser, const StringPiece16& name);
+ bool parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName);
+ std::unique_ptr<Attribute> parseAttrImpl(XmlPullParser* parser,
+ ResourceName* resourceName,
+ bool weak);
+ bool parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag,
+ Attribute::Symbol* outSymbol);
+ bool parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName);
+ bool parseUntypedItem(XmlPullParser* parser, Style& style);
+ bool parseDeclareStyleable(XmlPullParser* parser, const ResourceNameRef& resourceName);
+ bool parseArray(XmlPullParser* parser, const ResourceNameRef& resourceName, uint32_t typeMask);
+ bool parsePlural(XmlPullParser* parser, const ResourceNameRef& resourceName);
+
+ std::shared_ptr<ResourceTable> mTable;
+ Source mSource;
+ ConfigDescription mConfig;
+ SourceLogger mLogger;
+ std::shared_ptr<XmlPullParser> mParser;
+};
+
+} // namespace aapt
+
+#endif // AAPT_RESOURCE_PARSER_H
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
new file mode 100644
index 0000000..a93d0ff
--- /dev/null
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -0,0 +1,492 @@
+/*
+ * 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 "ResourceParser.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "SourceXmlPullParser.h"
+
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+
+TEST(ResourceParserReferenceTest, ParseReferenceWithNoPackage) {
+ ResourceNameRef expected = { {}, ResourceType::kColor, u"foo" };
+ ResourceNameRef actual;
+ bool create = false;
+ bool privateRef = false;
+ EXPECT_TRUE(ResourceParser::tryParseReference(u"@color/foo", &actual, &create, &privateRef));
+ EXPECT_EQ(expected, actual);
+ EXPECT_FALSE(create);
+ EXPECT_FALSE(privateRef);
+}
+
+TEST(ResourceParserReferenceTest, ParseReferenceWithPackage) {
+ ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
+ ResourceNameRef actual;
+ bool create = false;
+ bool privateRef = false;
+ EXPECT_TRUE(ResourceParser::tryParseReference(u"@android:color/foo", &actual, &create,
+ &privateRef));
+ EXPECT_EQ(expected, actual);
+ EXPECT_FALSE(create);
+ EXPECT_FALSE(privateRef);
+}
+
+TEST(ResourceParserReferenceTest, ParseReferenceWithSurroundingWhitespace) {
+ ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
+ ResourceNameRef actual;
+ bool create = false;
+ bool privateRef = false;
+ EXPECT_TRUE(ResourceParser::tryParseReference(u"\t @android:color/foo\n \n\t", &actual,
+ &create, &privateRef));
+ EXPECT_EQ(expected, actual);
+ EXPECT_FALSE(create);
+ EXPECT_FALSE(privateRef);
+}
+
+TEST(ResourceParserReferenceTest, ParseAutoCreateIdReference) {
+ ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
+ ResourceNameRef actual;
+ bool create = false;
+ bool privateRef = false;
+ EXPECT_TRUE(ResourceParser::tryParseReference(u"@+android:id/foo", &actual, &create,
+ &privateRef));
+ EXPECT_EQ(expected, actual);
+ EXPECT_TRUE(create);
+ EXPECT_FALSE(privateRef);
+}
+
+TEST(ResourceParserReferenceTest, ParsePrivateReference) {
+ ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
+ ResourceNameRef actual;
+ bool create = false;
+ bool privateRef = false;
+ EXPECT_TRUE(ResourceParser::tryParseReference(u"@*android:id/foo", &actual, &create,
+ &privateRef));
+ EXPECT_EQ(expected, actual);
+ EXPECT_FALSE(create);
+ EXPECT_TRUE(privateRef);
+}
+
+TEST(ResourceParserReferenceTest, FailToParseAutoCreateNonIdReference) {
+ bool create = false;
+ bool privateRef = false;
+ ResourceNameRef actual;
+ EXPECT_FALSE(ResourceParser::tryParseReference(u"@+android:color/foo", &actual, &create,
+ &privateRef));
+}
+
+TEST(ResourceParserReferenceTest, ParseStyleParentReference) {
+ Reference ref;
+ std::string errStr;
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@android:style/foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
+
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@style/foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
+
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?android:style/foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
+
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?style/foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
+
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:style/foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
+
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
+
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
+}
+
+struct ResourceParserTest : public ::testing::Test {
+ virtual void SetUp() override {
+ mTable = std::make_shared<ResourceTable>();
+ mTable->setPackage(u"android");
+ }
+
+ ::testing::AssertionResult testParse(const StringPiece& str) {
+ std::stringstream input(kXmlPreamble);
+ input << "<resources>\n" << str << "\n</resources>" << std::endl;
+ ResourceParser parser(mTable, Source{ "test" }, {},
+ std::make_shared<SourceXmlPullParser>(input));
+ if (parser.parse()) {
+ return ::testing::AssertionSuccess();
+ }
+ return ::testing::AssertionFailure();
+ }
+
+ template <typename T>
+ const T* findResource(const ResourceNameRef& name, const ConfigDescription& config) {
+ using std::begin;
+ using std::end;
+
+ const ResourceTableType* type;
+ const ResourceEntry* entry;
+ std::tie(type, entry) = mTable->findResource(name);
+ if (!type || !entry) {
+ return nullptr;
+ }
+
+ for (const auto& configValue : entry->values) {
+ if (configValue.config == config) {
+ return dynamic_cast<const T*>(configValue.value.get());
+ }
+ }
+ return nullptr;
+ }
+
+ template <typename T>
+ const T* findResource(const ResourceNameRef& name) {
+ return findResource<T>(name, {});
+ }
+
+ std::shared_ptr<ResourceTable> mTable;
+};
+
+TEST_F(ResourceParserTest, FailToParseWithNoRootResourcesElement) {
+ std::stringstream input(kXmlPreamble);
+ input << "<attr name=\"foo\"/>" << std::endl;
+ ResourceParser parser(mTable, {}, {}, std::make_shared<SourceXmlPullParser>(input));
+ ASSERT_FALSE(parser.parse());
+}
+
+TEST_F(ResourceParserTest, ParseQuotedString) {
+ std::string input = "<string name=\"foo\"> \" hey there \" </string>";
+ ASSERT_TRUE(testParse(input));
+
+ const String* str = findResource<String>(ResourceName{
+ u"android", ResourceType::kString, u"foo"});
+ ASSERT_NE(nullptr, str);
+ EXPECT_EQ(std::u16string(u" hey there "), *str->value);
+}
+
+TEST_F(ResourceParserTest, ParseEscapedString) {
+ std::string input = "<string name=\"foo\">\\?123</string>";
+ ASSERT_TRUE(testParse(input));
+
+ const String* str = findResource<String>(ResourceName{
+ u"android", ResourceType::kString, u"foo" });
+ ASSERT_NE(nullptr, str);
+ EXPECT_EQ(std::u16string(u"?123"), *str->value);
+}
+
+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.
+ const BinaryPrimitive* integer = findResource<BinaryPrimitive>(ResourceName{
+ u"android", ResourceType::kInteger, u"foo" });
+ ASSERT_NE(nullptr, integer);
+ EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), integer->value.dataType);
+ EXPECT_EQ(0u, integer->value.data);
+}
+
+TEST_F(ResourceParserTest, ParseEmpty) {
+ std::string input = "<integer name=\"foo\">@empty</integer>";
+ ASSERT_TRUE(testParse(input));
+
+ const BinaryPrimitive* integer = findResource<BinaryPrimitive>(ResourceName{
+ u"android", ResourceType::kInteger, u"foo" });
+ 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);
+}
+
+TEST_F(ResourceParserTest, ParseAttr) {
+ std::string input = "<attr name=\"foo\" format=\"string\"/>\n"
+ "<attr name=\"bar\"/>";
+ ASSERT_TRUE(testParse(input));
+
+ const Attribute* attr = findResource<Attribute>(ResourceName{
+ u"android", ResourceType::kAttr, u"foo"});
+ EXPECT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
+
+ attr = findResource<Attribute>(ResourceName{
+ u"android", ResourceType::kAttr, u"bar"});
+ EXPECT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask);
+}
+
+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));
+
+ const Attribute* attr = findResource<Attribute>(ResourceName{
+ u"android", ResourceType::kAttr, u"foo"});
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
+}
+
+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));
+
+ const Attribute* attr = findResource<Attribute>(ResourceName{
+ u"android", ResourceType::kAttr, u"foo"});
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask);
+}
+
+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));
+
+ const Attribute* enumAttr = findResource<Attribute>(ResourceName{
+ u"android", ResourceType::kAttr, u"foo"});
+ ASSERT_NE(enumAttr, nullptr);
+ EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM);
+ ASSERT_EQ(enumAttr->symbols.size(), 3u);
+
+ EXPECT_EQ(enumAttr->symbols[0].symbol.name.entry, u"bar");
+ EXPECT_EQ(enumAttr->symbols[0].value, 0u);
+
+ EXPECT_EQ(enumAttr->symbols[1].symbol.name.entry, u"bat");
+ EXPECT_EQ(enumAttr->symbols[1].value, 1u);
+
+ EXPECT_EQ(enumAttr->symbols[2].symbol.name.entry, u"baz");
+ EXPECT_EQ(enumAttr->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));
+
+ const Attribute* flagAttr = findResource<Attribute>(ResourceName{
+ u"android", ResourceType::kAttr, u"foo"});
+ ASSERT_NE(flagAttr, nullptr);
+ EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS);
+ ASSERT_EQ(flagAttr->symbols.size(), 3u);
+
+ EXPECT_EQ(flagAttr->symbols[0].symbol.name.entry, u"bar");
+ EXPECT_EQ(flagAttr->symbols[0].value, 0u);
+
+ EXPECT_EQ(flagAttr->symbols[1].symbol.name.entry, u"bat");
+ EXPECT_EQ(flagAttr->symbols[1].value, 1u);
+
+ EXPECT_EQ(flagAttr->symbols[2].symbol.name.entry, u"baz");
+ EXPECT_EQ(flagAttr->symbols[2].value, 2u);
+
+ std::unique_ptr<BinaryPrimitive> flagValue =
+ ResourceParser::tryParseFlagSymbol(*flagAttr, u"baz|bat");
+ ASSERT_NE(flagValue, nullptr);
+ EXPECT_EQ(flagValue->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));
+}
+
+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));
+
+ const Style* style = findResource<Style>(ResourceName{
+ u"android", ResourceType::kStyle, u"foo"});
+ ASSERT_NE(style, nullptr);
+ EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"fu"), style->parent.name);
+ ASSERT_EQ(style->entries.size(), 3u);
+
+ EXPECT_EQ(style->entries[0].key.name,
+ (ResourceName{ u"android", ResourceType::kAttr, u"bar" }));
+ EXPECT_EQ(style->entries[1].key.name,
+ (ResourceName{ u"android", ResourceType::kAttr, u"bat" }));
+ EXPECT_EQ(style->entries[2].key.name,
+ (ResourceName{ u"android", ResourceType::kAttr, u"baz" }));
+}
+
+TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) {
+ std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>";
+ ASSERT_TRUE(testParse(input));
+
+ const Style* style = findResource<Style>(
+ ResourceName{ u"android", ResourceType::kStyle, u"foo" });
+ ASSERT_NE(style, nullptr);
+ EXPECT_EQ(ResourceNameRef(u"com.app", ResourceType::kStyle, u"Theme"), style->parent.name);
+}
+
+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));
+
+ const Style* style = findResource<Style>(ResourceName{
+ u"android", ResourceType::kStyle, u"foo" });
+ ASSERT_NE(style, nullptr);
+ EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"Theme"), style->parent.name);
+}
+
+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));
+
+ const Style* style = findResource<Style>(ResourceName{
+ u"android", ResourceType::kStyle, u"foo" });
+ ASSERT_NE(style, nullptr);
+ ASSERT_EQ(1u, style->entries.size());
+ EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kAttr, u"bar"),
+ style->entries[0].key.name);
+}
+
+TEST_F(ResourceParserTest, ParseStyleWithInferredParent) {
+ std::string input = "<style name=\"foo.bar\"/>";
+ ASSERT_TRUE(testParse(input));
+
+ const Style* style = findResource<Style>(ResourceName{
+ u"android", ResourceType::kStyle, u"foo.bar" });
+ ASSERT_NE(style, nullptr);
+ EXPECT_EQ(style->parent.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
+ EXPECT_TRUE(style->parentInferred);
+}
+
+TEST_F(ResourceParserTest, ParseStyleWithInferredParentOverridenByEmptyParentAttribute) {
+ std::string input = "<style name=\"foo.bar\" parent=\"\"/>";
+ ASSERT_TRUE(testParse(input));
+
+ const Style* style = findResource<Style>(ResourceName{
+ u"android", ResourceType::kStyle, u"foo.bar" });
+ ASSERT_NE(style, nullptr);
+ EXPECT_FALSE(style->parent.name.isValid());
+ EXPECT_FALSE(style->parentInferred);
+}
+
+TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) {
+ std::string input = "<string name=\"foo\">@+id/bar</string>";
+ ASSERT_TRUE(testParse(input));
+
+ const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"bar"});
+ 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"
+ "</declare-styleable>";
+ ASSERT_TRUE(testParse(input));
+
+ const Attribute* attr = findResource<Attribute>(ResourceName{
+ u"android", ResourceType::kAttr, u"bar"});
+ ASSERT_NE(attr, nullptr);
+ EXPECT_TRUE(attr->isWeak());
+
+ attr = findResource<Attribute>(ResourceName{ u"android", ResourceType::kAttr, u"bat"});
+ ASSERT_NE(attr, nullptr);
+ EXPECT_TRUE(attr->isWeak());
+
+ const Styleable* styleable = findResource<Styleable>(ResourceName{
+ u"android", ResourceType::kStyleable, u"foo" });
+ ASSERT_NE(styleable, nullptr);
+ ASSERT_EQ(2u, styleable->entries.size());
+
+ EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bar"}), styleable->entries[0].name);
+ EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bat"}), styleable->entries[1].name);
+}
+
+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));
+
+ const Array* array = findResource<Array>(ResourceName{
+ u"android", ResourceType::kArray, u"foo" });
+ ASSERT_NE(array, nullptr);
+ ASSERT_EQ(3u, array->items.size());
+
+ EXPECT_NE(nullptr, dynamic_cast<const Reference*>(array->items[0].get()));
+ EXPECT_NE(nullptr, dynamic_cast<const String*>(array->items[1].get()));
+ EXPECT_NE(nullptr, dynamic_cast<const BinaryPrimitive*>(array->items[2].get()));
+}
+
+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));
+}
+
+TEST_F(ResourceParserTest, ParseCommentsWithResource) {
+ std::string input = "<!-- This is a comment -->\n"
+ "<string name=\"foo\">Hi</string>";
+ ASSERT_TRUE(testParse(input));
+
+ const ResourceTableType* type;
+ const ResourceEntry* entry;
+ std::tie(type, entry) = mTable->findResource(ResourceName{
+ u"android", ResourceType::kString, u"foo"});
+ ASSERT_NE(type, nullptr);
+ ASSERT_NE(entry, nullptr);
+ ASSERT_FALSE(entry->values.empty());
+ EXPECT_EQ(entry->values.front().comment, u"This is a comment");
+}
+
+/*
+ * Declaring an ID as public should not require a separate definition
+ * (as an ID has no value).
+ */
+TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) {
+ std::string input = "<public type=\"id\" name=\"foo\"/>";
+ ASSERT_TRUE(testParse(input));
+
+ const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"foo" });
+ ASSERT_NE(nullptr, id);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
new file mode 100644
index 0000000..c93ecc7
--- /dev/null
+++ b/tools/aapt2/ResourceTable.cpp
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ConfigDescription.h"
+#include "Logger.h"
+#include "NameMangler.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "Util.h"
+
+#include <algorithm>
+#include <androidfw/ResourceTypes.h>
+#include <memory>
+#include <string>
+#include <tuple>
+
+namespace aapt {
+
+static bool compareConfigs(const ResourceConfigValue& lhs, const ConfigDescription& rhs) {
+ return lhs.config < rhs;
+}
+
+static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) {
+ return lhs->type < rhs;
+}
+
+static bool lessThanEntry(const std::unique_ptr<ResourceEntry>& lhs, const StringPiece16& rhs) {
+ return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
+}
+
+ResourceTable::ResourceTable() : mPackageId(kUnsetPackageId) {
+ // Make sure attrs always have type ID 1.
+ findOrCreateType(ResourceType::kAttr)->typeId = 1;
+}
+
+std::unique_ptr<ResourceTableType>& ResourceTable::findOrCreateType(ResourceType type) {
+ auto last = mTypes.end();
+ auto iter = std::lower_bound(mTypes.begin(), last, type, lessThanType);
+ if (iter != last) {
+ if ((*iter)->type == type) {
+ return *iter;
+ }
+ }
+ return *mTypes.emplace(iter, new ResourceTableType{ type });
+}
+
+std::unique_ptr<ResourceEntry>& ResourceTable::findOrCreateEntry(
+ std::unique_ptr<ResourceTableType>& type, const StringPiece16& name) {
+ auto last = type->entries.end();
+ auto iter = std::lower_bound(type->entries.begin(), last, name, lessThanEntry);
+ if (iter != last) {
+ if (name == (*iter)->name) {
+ return *iter;
+ }
+ }
+ return *type->entries.emplace(iter, new ResourceEntry{ name });
+}
+
+struct IsAttributeVisitor : ConstValueVisitor {
+ bool isAttribute = false;
+
+ void visit(const Attribute&, ValueVisitorArgs&) override {
+ isAttribute = true;
+ }
+
+ operator bool() {
+ return isAttribute;
+ }
+};
+
+/**
+ * The default handler for collisions. A return value of -1 means keep the
+ * existing value, 0 means fail, and +1 means take the incoming value.
+ */
+static int defaultCollisionHandler(const Value& existing, const Value& incoming) {
+ IsAttributeVisitor existingIsAttr, incomingIsAttr;
+ existing.accept(existingIsAttr, {});
+ incoming.accept(incomingIsAttr, {});
+
+ if (!incomingIsAttr) {
+ 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 (!existingIsAttr) {
+ 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;
+ }
+
+ //
+ // Attribute specific handling. At this point we know both
+ // values are attributes. Since we can declare and define
+ // attributes all-over, we do special handling to see
+ // which definition sticks.
+ //
+ const Attribute& existingAttr = static_cast<const Attribute&>(existing);
+ const Attribute& incomingAttr = static_cast<const Attribute&>(incoming);
+ if (existingAttr.typeMask == incomingAttr.typeMask) {
+ // 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;
+}
+
+static constexpr const char16_t* kValidNameChars = u"._-";
+static constexpr const char16_t* kValidNameMangledChars = u"._-$";
+
+bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config,
+ const SourceLine& source, std::unique_ptr<Value> value) {
+ return addResourceImpl(name, ResourceId{}, config, source, std::move(value), kValidNameChars);
+}
+
+bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId,
+ const ConfigDescription& config, const SourceLine& source,
+ std::unique_ptr<Value> value) {
+ return addResourceImpl(name, resId, config, source, std::move(value), kValidNameChars);
+}
+
+bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const SourceLine& source,
+ std::unique_ptr<Value> value) {
+ return addResourceImpl(name, ResourceId{}, config, source, std::move(value),
+ kValidNameMangledChars);
+}
+
+bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
+ const ConfigDescription& config, const SourceLine& source,
+ std::unique_ptr<Value> value, const char16_t* validChars) {
+ if (!name.package.empty() && name.package != mPackage) {
+ Logger::error(source)
+ << "resource '"
+ << name
+ << "' has incompatible package. Must be '"
+ << mPackage
+ << "'."
+ << std::endl;
+ return false;
+ }
+
+ auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
+ if (badCharIter != name.entry.end()) {
+ Logger::error(source)
+ << "resource '"
+ << name
+ << "' has invalid entry name '"
+ << name.entry
+ << "'. Invalid character '"
+ << StringPiece16(badCharIter, 1)
+ << "'."
+ << std::endl;
+ return false;
+ }
+
+ std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type);
+ if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId &&
+ type->typeId != resId.typeId()) {
+ Logger::error(source)
+ << "trying to add resource '"
+ << name
+ << "' with ID "
+ << resId
+ << " but type '"
+ << type->type
+ << "' already has ID "
+ << std::hex << type->typeId << std::dec
+ << "."
+ << std::endl;
+ return false;
+ }
+
+ std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry);
+ if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId &&
+ entry->entryId != resId.entryId()) {
+ Logger::error(source)
+ << "trying to add resource '"
+ << name
+ << "' with ID "
+ << resId
+ << " but resource already has ID "
+ << ResourceId(mPackageId, type->typeId, entry->entryId)
+ << "."
+ << std::endl;
+ return false;
+ }
+
+ const auto endIter = std::end(entry->values);
+ auto iter = std::lower_bound(std::begin(entry->values), endIter, config, compareConfigs);
+ if (iter == endIter || iter->config != config) {
+ // This resource did not exist before, add it.
+ entry->values.insert(iter, ResourceConfigValue{ config, source, {}, std::move(value) });
+ } else {
+ int collisionResult = defaultCollisionHandler(*iter->value, *value);
+ if (collisionResult > 0) {
+ // Take the incoming value.
+ *iter = ResourceConfigValue{ config, source, {}, std::move(value) };
+ } else if (collisionResult == 0) {
+ Logger::error(source)
+ << "duplicate value for resource '" << name << "' "
+ << "with config '" << iter->config << "'."
+ << std::endl;
+
+ Logger::error(iter->source)
+ << "resource previously defined here."
+ << std::endl;
+ return false;
+ }
+ }
+
+ if (resId.isValid()) {
+ type->typeId = resId.typeId();
+ entry->entryId = resId.entryId();
+ }
+ return true;
+}
+
+bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId resId,
+ const SourceLine& source) {
+ return markPublicImpl(name, resId, source, kValidNameChars);
+}
+
+bool ResourceTable::markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId,
+ const SourceLine& source) {
+ return markPublicImpl(name, resId, source, kValidNameMangledChars);
+}
+
+bool ResourceTable::markPublicImpl(const ResourceNameRef& name, const ResourceId resId,
+ const SourceLine& source, const char16_t* validChars) {
+ if (!name.package.empty() && name.package != mPackage) {
+ Logger::error(source)
+ << "resource '"
+ << name
+ << "' has incompatible package. Must be '"
+ << mPackage
+ << "'."
+ << std::endl;
+ return false;
+ }
+
+ auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
+ if (badCharIter != name.entry.end()) {
+ Logger::error(source)
+ << "resource '"
+ << name
+ << "' has invalid entry name '"
+ << name.entry
+ << "'. Invalid character '"
+ << StringPiece16(badCharIter, 1)
+ << "'."
+ << std::endl;
+ return false;
+ }
+
+ std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type);
+ if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId &&
+ type->typeId != resId.typeId()) {
+ Logger::error(source)
+ << "trying to make resource '"
+ << name
+ << "' public with ID "
+ << resId
+ << " but type '"
+ << type->type
+ << "' already has ID "
+ << std::hex << type->typeId << std::dec
+ << "."
+ << std::endl;
+ return false;
+ }
+
+ std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry);
+ if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId &&
+ entry->entryId != resId.entryId()) {
+ Logger::error(source)
+ << "trying to make resource '"
+ << name
+ << "' public with ID "
+ << resId
+ << " but resource already has ID "
+ << ResourceId(mPackageId, type->typeId, entry->entryId)
+ << "."
+ << std::endl;
+ return false;
+ }
+
+ type->publicStatus.isPublic = true;
+ entry->publicStatus.isPublic = true;
+ entry->publicStatus.source = source;
+
+ if (resId.isValid()) {
+ type->typeId = resId.typeId();
+ entry->entryId = resId.entryId();
+ }
+ return true;
+}
+
+bool ResourceTable::merge(ResourceTable&& other) {
+ const bool mangleNames = mPackage != other.getPackage();
+ std::u16string mangledName;
+
+ for (auto& otherType : other) {
+ std::unique_ptr<ResourceTableType>& type = findOrCreateType(otherType->type);
+ if (otherType->publicStatus.isPublic) {
+ if (type->publicStatus.isPublic && type->typeId != otherType->typeId) {
+ Logger::error() << "can not merge type '" << type->type
+ << "': conflicting public IDs "
+ << "(" << type->typeId << " vs " << otherType->typeId << ")."
+ << std::endl;
+ return false;
+ }
+ type->publicStatus = std::move(otherType->publicStatus);
+ type->typeId = otherType->typeId;
+ }
+
+ for (auto& otherEntry : otherType->entries) {
+ const std::u16string* nameToAdd = &otherEntry->name;
+ if (mangleNames) {
+ mangledName = otherEntry->name;
+ NameMangler::mangle(other.getPackage(), &mangledName);
+ nameToAdd = &mangledName;
+ }
+
+ std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, *nameToAdd);
+ if (otherEntry->publicStatus.isPublic) {
+ if (entry->publicStatus.isPublic && entry->entryId != otherEntry->entryId) {
+ Logger::error() << "can not merge entry '" << type->type << "/" << entry->name
+ << "': conflicting public IDs "
+ << "(" << entry->entryId << " vs " << entry->entryId << ")."
+ << std::endl;
+ return false;
+ }
+ entry->publicStatus = std::move(otherEntry->publicStatus);
+ entry->entryId = otherEntry->entryId;
+ }
+
+ for (ResourceConfigValue& otherValue : otherEntry->values) {
+ auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
+ otherValue.config, compareConfigs);
+ if (iter != entry->values.end() && iter->config == otherValue.config) {
+ int collisionResult = defaultCollisionHandler(*iter->value, *otherValue.value);
+ if (collisionResult > 0) {
+ // Take the incoming value.
+ iter->source = std::move(otherValue.source);
+ iter->comment = std::move(otherValue.comment);
+ iter->value = std::unique_ptr<Value>(otherValue.value->clone(&mValuePool));
+ } else if (collisionResult == 0) {
+ ResourceNameRef resourceName = { mPackage, type->type, entry->name };
+ Logger::error(otherValue.source)
+ << "resource '" << resourceName << "' has a conflicting value for "
+ << "configuration (" << otherValue.config << ")."
+ << std::endl;
+ Logger::note(iter->source) << "originally defined here." << std::endl;
+ return false;
+ }
+ } else {
+ entry->values.insert(iter, ResourceConfigValue{
+ otherValue.config,
+ std::move(otherValue.source),
+ std::move(otherValue.comment),
+ std::unique_ptr<Value>(otherValue.value->clone(&mValuePool)),
+ });
+ }
+ }
+ }
+ }
+ return true;
+}
+
+std::tuple<const ResourceTableType*, const ResourceEntry*>
+ResourceTable::findResource(const ResourceNameRef& name) const {
+ if (name.package != mPackage) {
+ return {};
+ }
+
+ auto iter = std::lower_bound(mTypes.begin(), mTypes.end(), name.type, lessThanType);
+ if (iter == mTypes.end() || (*iter)->type != name.type) {
+ return {};
+ }
+
+ const std::unique_ptr<ResourceTableType>& type = *iter;
+ auto iter2 = std::lower_bound(type->entries.begin(), type->entries.end(), name.entry,
+ lessThanEntry);
+ if (iter2 == type->entries.end() || name.entry != (*iter2)->name) {
+ return {};
+ }
+ return std::make_tuple(iter->get(), iter2->get());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
new file mode 100644
index 0000000..706f56a
--- /dev/null
+++ b/tools/aapt2/ResourceTable.h
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_RESOURCE_TABLE_H
+#define AAPT_RESOURCE_TABLE_H
+
+#include "ConfigDescription.h"
+#include "Resource.h"
+#include "ResourceValues.h"
+#include "Source.h"
+#include "StringPool.h"
+
+#include <memory>
+#include <string>
+#include <tuple>
+#include <vector>
+
+namespace aapt {
+
+/**
+ * The Public status of a resource.
+ */
+struct Public {
+ bool isPublic = false;
+ SourceLine source;
+ std::u16string comment;
+};
+
+/**
+ * The resource value for a specific configuration.
+ */
+struct ResourceConfigValue {
+ ConfigDescription config;
+ SourceLine source;
+ std::u16string comment;
+ std::unique_ptr<Value> value;
+};
+
+/**
+ * Represents a resource entry, which may have
+ * varying values for each defined configuration.
+ */
+struct ResourceEntry {
+ enum {
+ kUnsetEntryId = 0xffffffffu
+ };
+
+ /**
+ * 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.
+ */
+ size_t entryId;
+
+ /**
+ * Whether this resource is public (and must maintain the same
+ * entry ID across builds).
+ */
+ Public publicStatus;
+
+ /**
+ * The resource's values for each configuration.
+ */
+ std::vector<ResourceConfigValue> values;
+
+ inline ResourceEntry(const StringPiece16& _name);
+ inline ResourceEntry(const ResourceEntry* rhs);
+};
+
+/**
+ * Represents a resource type, which holds entries defined
+ * for this type.
+ */
+struct ResourceTableType {
+ enum {
+ kUnsetTypeId = 0xffffffffu
+ };
+
+ /**
+ * The logical type of resource (string, drawable, layout, etc.).
+ */
+ const ResourceType type;
+
+ /**
+ * The type ID for this resource.
+ */
+ size_t typeId;
+
+ /**
+ * Whether this type is public (and must maintain the same
+ * type ID across builds).
+ */
+ Public publicStatus;
+
+ /**
+ * List of resources for this type.
+ */
+ std::vector<std::unique_ptr<ResourceEntry>> entries;
+
+ ResourceTableType(const ResourceType _type);
+ ResourceTableType(const ResourceTableType* rhs);
+};
+
+/**
+ * The container and index for all resources defined for an app. This gets
+ * flattened into a binary resource table (resources.arsc).
+ */
+class ResourceTable {
+public:
+ using iterator = std::vector<std::unique_ptr<ResourceTableType>>::iterator;
+ using const_iterator = std::vector<std::unique_ptr<ResourceTableType>>::const_iterator;
+
+ enum {
+ kUnsetPackageId = 0xffffffff
+ };
+
+ ResourceTable();
+
+ size_t getPackageId() const;
+ void setPackageId(size_t packageId);
+
+ const std::u16string& getPackage() const;
+ void setPackage(const StringPiece16& package);
+
+ bool addResource(const ResourceNameRef& name, const ConfigDescription& config,
+ const SourceLine& source, std::unique_ptr<Value> value);
+
+ /**
+ * Same as addResource, but doesn't verify the validity of the name. This is used
+ * when loading resources from an existing binary resource table that may have mangled
+ * names.
+ */
+ bool addResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
+ const SourceLine& source, std::unique_ptr<Value> value);
+
+ bool addResource(const ResourceNameRef& name, const ResourceId resId,
+ const ConfigDescription& config, const SourceLine& source,
+ std::unique_ptr<Value> value);
+
+ bool markPublic(const ResourceNameRef& name, const ResourceId resId, const SourceLine& source);
+ bool markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId,
+ const SourceLine& source);
+
+ /*
+ * Merges the resources from `other` into this table, mangling the names of the resources
+ * if `other` has a different package name.
+ */
+ bool merge(ResourceTable&& other);
+
+ /**
+ * Returns the string pool used by this ResourceTable.
+ * Values that reference strings should use this pool to create
+ * their strings.
+ */
+ StringPool& getValueStringPool();
+ const StringPool& getValueStringPool() const;
+
+ std::tuple<const ResourceTableType*, const ResourceEntry*>
+ findResource(const ResourceNameRef& name) const;
+
+ iterator begin();
+ iterator end();
+ const_iterator begin() const;
+ const_iterator end() const;
+
+private:
+ std::unique_ptr<ResourceTableType>& findOrCreateType(ResourceType type);
+ std::unique_ptr<ResourceEntry>& findOrCreateEntry(std::unique_ptr<ResourceTableType>& type,
+ const StringPiece16& name);
+
+ bool addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
+ const ConfigDescription& config, const SourceLine& source,
+ std::unique_ptr<Value> value, const char16_t* validChars);
+ bool markPublicImpl(const ResourceNameRef& name, const ResourceId resId,
+ const SourceLine& source, const char16_t* validChars);
+
+ std::u16string mPackage;
+ size_t mPackageId;
+
+ // StringPool must come before mTypes so that it is destroyed after.
+ // When StringPool references are destroyed (as they will be when mTypes
+ // is destroyed), they decrement a refCount, which would cause invalid
+ // memory access if the pool was already destroyed.
+ StringPool mValuePool;
+
+ std::vector<std::unique_ptr<ResourceTableType>> mTypes;
+};
+
+//
+// ResourceEntry implementation.
+//
+
+inline ResourceEntry::ResourceEntry(const StringPiece16& _name) :
+ name(_name.toString()), entryId(kUnsetEntryId) {
+}
+
+inline ResourceEntry::ResourceEntry(const ResourceEntry* rhs) :
+ name(rhs->name), entryId(rhs->entryId), publicStatus(rhs->publicStatus) {
+}
+
+//
+// ResourceTableType implementation.
+//
+
+inline ResourceTableType::ResourceTableType(const ResourceType _type) :
+ type(_type), typeId(kUnsetTypeId) {
+}
+
+inline ResourceTableType::ResourceTableType(const ResourceTableType* rhs) :
+ type(rhs->type), typeId(rhs->typeId), publicStatus(rhs->publicStatus) {
+}
+
+//
+// ResourceTable implementation.
+//
+
+inline StringPool& ResourceTable::getValueStringPool() {
+ return mValuePool;
+}
+
+inline const StringPool& ResourceTable::getValueStringPool() const {
+ return mValuePool;
+}
+
+inline ResourceTable::iterator ResourceTable::begin() {
+ return mTypes.begin();
+}
+
+inline ResourceTable::iterator ResourceTable::end() {
+ return mTypes.end();
+}
+
+inline ResourceTable::const_iterator ResourceTable::begin() const {
+ return mTypes.begin();
+}
+
+inline ResourceTable::const_iterator ResourceTable::end() const {
+ return mTypes.end();
+}
+
+inline const std::u16string& ResourceTable::getPackage() const {
+ return mPackage;
+}
+
+inline size_t ResourceTable::getPackageId() const {
+ return mPackageId;
+}
+
+inline void ResourceTable::setPackage(const StringPiece16& package) {
+ mPackage = package.toString();
+}
+
+inline void ResourceTable::setPackageId(size_t packageId) {
+ mPackageId = packageId;
+}
+
+} // namespace aapt
+
+#endif // AAPT_RESOURCE_TABLE_H
diff --git a/tools/aapt2/ResourceTableResolver.cpp b/tools/aapt2/ResourceTableResolver.cpp
new file mode 100644
index 0000000..910c2c0
--- /dev/null
+++ b/tools/aapt2/ResourceTableResolver.cpp
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Maybe.h"
+#include "NameMangler.h"
+#include "Resource.h"
+#include "ResourceTable.h"
+#include "ResourceTableResolver.h"
+#include "ResourceValues.h"
+#include "Util.h"
+
+#include <androidfw/AssetManager.h>
+#include <androidfw/ResourceTypes.h>
+#include <memory>
+#include <vector>
+
+namespace aapt {
+
+ResourceTableResolver::ResourceTableResolver(
+ std::shared_ptr<const ResourceTable> table,
+ const std::vector<std::shared_ptr<const android::AssetManager>>& sources) :
+ mTable(table), mSources(sources) {
+ for (const auto& assetManager : mSources) {
+ const android::ResTable& resTable = assetManager->getResources(false);
+ const size_t packageCount = resTable.getBasePackageCount();
+ for (size_t i = 0; i < packageCount; i++) {
+ std::u16string packageName = resTable.getBasePackageName(i).string();
+ mIncludedPackages.insert(std::move(packageName));
+ }
+ }
+}
+
+Maybe<ResourceId> ResourceTableResolver::findId(const ResourceName& name) {
+ Maybe<Entry> result = findAttribute(name);
+ if (result) {
+ return result.value().id;
+ }
+ return {};
+}
+
+Maybe<IResolver::Entry> ResourceTableResolver::findAttribute(const ResourceName& name) {
+ auto cacheIter = mCache.find(name);
+ if (cacheIter != std::end(mCache)) {
+ return Entry{ cacheIter->second.id, cacheIter->second.attr.get() };
+ }
+
+ ResourceName mangledName;
+ const ResourceName* nameToSearch = &name;
+ if (name.package != mTable->getPackage()) {
+ // This may be a reference to an included resource or
+ // to a mangled resource.
+ if (mIncludedPackages.find(name.package) == mIncludedPackages.end()) {
+ // This is not in our included set, so mangle the name and
+ // check for that.
+ mangledName.entry = name.entry;
+ NameMangler::mangle(name.package, &mangledName.entry);
+ mangledName.package = mTable->getPackage();
+ mangledName.type = name.type;
+ nameToSearch = &mangledName;
+ } else {
+ const CacheEntry* cacheEntry = buildCacheEntry(name);
+ if (cacheEntry) {
+ return Entry{ cacheEntry->id, cacheEntry->attr.get() };
+ }
+ return {};
+ }
+ }
+
+ const ResourceTableType* type;
+ const ResourceEntry* entry;
+ std::tie(type, entry) = mTable->findResource(*nameToSearch);
+ if (type && entry) {
+ Entry result = {};
+ if (mTable->getPackageId() != ResourceTable::kUnsetPackageId &&
+ type->typeId != ResourceTableType::kUnsetTypeId &&
+ entry->entryId != ResourceEntry::kUnsetEntryId) {
+ result.id = ResourceId(mTable->getPackageId(), type->typeId, entry->entryId);
+ }
+
+ if (!entry->values.empty()) {
+ visitFunc<Attribute>(*entry->values.front().value, [&result](Attribute& attr) {
+ result.attr = &attr;
+ });
+ }
+ return result;
+ }
+ return {};
+}
+
+Maybe<ResourceName> ResourceTableResolver::findName(ResourceId resId) {
+ for (const auto& assetManager : mSources) {
+ const android::ResTable& table = assetManager->getResources(false);
+
+ android::ResTable::resource_name resourceName;
+ if (!table.getResourceName(resId.id, false, &resourceName)) {
+ continue;
+ }
+
+ const ResourceType* type = parseResourceType(StringPiece16(resourceName.type,
+ resourceName.typeLen));
+ assert(type);
+ return ResourceName{
+ { resourceName.package, resourceName.packageLen },
+ *type,
+ { resourceName.name, resourceName.nameLen } };
+ }
+ return {};
+}
+
+/**
+ * This is called when we need to lookup a resource name in the AssetManager.
+ * Since the values in the AssetManager are not parsed like in a ResourceTable,
+ * we must create Attribute objects here if we find them.
+ */
+const ResourceTableResolver::CacheEntry* ResourceTableResolver::buildCacheEntry(
+ const ResourceName& name) {
+ for (const auto& assetManager : mSources) {
+ const android::ResTable& table = assetManager->getResources(false);
+
+ const StringPiece16 type16 = toString(name.type);
+ ResourceId resId {
+ table.identifierForName(
+ name.entry.data(), name.entry.size(),
+ type16.data(), type16.size(),
+ name.package.data(), name.package.size())
+ };
+
+ if (!resId.isValid()) {
+ continue;
+ }
+
+ CacheEntry& entry = mCache[name];
+ entry.id = resId;
+
+ //
+ // Now check to see if this resource is an Attribute.
+ //
+
+ const android::ResTable::bag_entry* bagBegin;
+ ssize_t bags = table.lockBag(resId.id, &bagBegin);
+ if (bags < 1) {
+ table.unlockBag(bagBegin);
+ return &entry;
+ }
+
+ // Look for the ATTR_TYPE key in the bag and check the types it supports.
+ uint32_t attrTypeMask = 0;
+ for (ssize_t i = 0; i < bags; i++) {
+ if (bagBegin[i].map.name.ident == android::ResTable_map::ATTR_TYPE) {
+ attrTypeMask = bagBegin[i].map.value.data;
+ }
+ }
+
+ entry.attr = util::make_unique<Attribute>(false);
+
+ if (attrTypeMask & android::ResTable_map::TYPE_ENUM ||
+ attrTypeMask & android::ResTable_map::TYPE_FLAGS) {
+ for (ssize_t i = 0; i < bags; i++) {
+ if (Res_INTERNALID(bagBegin[i].map.name.ident)) {
+ // Internal IDs are special keys, which are not enum/flag symbols, so skip.
+ continue;
+ }
+
+ android::ResTable::resource_name symbolName;
+ bool result = table.getResourceName(bagBegin[i].map.name.ident, false,
+ &symbolName);
+ assert(result);
+ const ResourceType* type = parseResourceType(
+ StringPiece16(symbolName.type, symbolName.typeLen));
+ assert(type);
+
+ entry.attr->symbols.push_back(Attribute::Symbol{
+ Reference(ResourceNameRef(
+ StringPiece16(symbolName.package, symbolName.packageLen),
+ *type,
+ StringPiece16(symbolName.name, symbolName.nameLen))),
+ bagBegin[i].map.value.data
+ });
+ }
+ }
+
+ entry.attr->typeMask |= attrTypeMask;
+ table.unlockBag(bagBegin);
+ return &entry;
+ }
+ return nullptr;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResourceTableResolver.h b/tools/aapt2/ResourceTableResolver.h
new file mode 100644
index 0000000..8f6b0b5
--- /dev/null
+++ b/tools/aapt2/ResourceTableResolver.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_RESOURCE_TABLE_RESOLVER_H
+#define AAPT_RESOURCE_TABLE_RESOLVER_H
+
+#include "Maybe.h"
+#include "Resolver.h"
+#include "Resource.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+
+#include <androidfw/AssetManager.h>
+#include <memory>
+#include <vector>
+#include <unordered_set>
+
+namespace aapt {
+
+/**
+ * Encapsulates the search of library sources as well as the local ResourceTable.
+ */
+class ResourceTableResolver : public IResolver {
+public:
+ /**
+ * Creates a resolver with a local ResourceTable and an AssetManager
+ * loaded with library packages.
+ */
+ ResourceTableResolver(
+ std::shared_ptr<const ResourceTable> table,
+ const std::vector<std::shared_ptr<const android::AssetManager>>& sources);
+
+ ResourceTableResolver(const ResourceTableResolver&) = delete; // Not copyable.
+
+ virtual Maybe<ResourceId> findId(const ResourceName& name) override;
+
+ virtual Maybe<Entry> findAttribute(const ResourceName& name) override;
+
+ virtual Maybe<ResourceName> findName(ResourceId resId) override;
+
+private:
+ struct CacheEntry {
+ ResourceId id;
+ std::unique_ptr<Attribute> attr;
+ };
+
+ const CacheEntry* buildCacheEntry(const ResourceName& name);
+
+ std::shared_ptr<const ResourceTable> mTable;
+ std::vector<std::shared_ptr<const android::AssetManager>> mSources;
+ std::map<ResourceName, CacheEntry> mCache;
+ std::unordered_set<std::u16string> mIncludedPackages;
+};
+
+} // namespace aapt
+
+#endif // AAPT_RESOURCE_TABLE_RESOLVER_H
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
new file mode 100644
index 0000000..06d8699
--- /dev/null
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "Util.h"
+
+#include <algorithm>
+#include <gtest/gtest.h>
+#include <ostream>
+#include <string>
+
+namespace aapt {
+
+struct TestValue : public Value {
+ std::u16string value;
+
+ TestValue(StringPiece16 str) : value(str.toString()) {
+ }
+
+ TestValue* clone(StringPool* /*newPool*/) const override {
+ return new TestValue(value);
+ }
+
+ void print(std::ostream& out) const override {
+ out << "(test) " << value;
+ }
+
+ virtual void accept(ValueVisitor&, ValueVisitorArgs&&) override {}
+ virtual void accept(ConstValueVisitor&, ValueVisitorArgs&&) const override {}
+};
+
+struct TestWeakValue : public Value {
+ bool isWeak() const override {
+ return true;
+ }
+
+ TestWeakValue* clone(StringPool* /*newPool*/) const override {
+ return new TestWeakValue();
+ }
+
+ void print(std::ostream& out) const override {
+ out << "(test) [weak]";
+ }
+
+ virtual void accept(ValueVisitor&, ValueVisitorArgs&&) override {}
+ virtual void accept(ConstValueVisitor&, ValueVisitorArgs&&) const override {}
+};
+
+TEST(ResourceTableTest, FailToAddResourceWithBadName) {
+ ResourceTable table;
+ table.setPackage(u"android");
+
+ EXPECT_FALSE(table.addResource(
+ ResourceNameRef{ u"android", ResourceType::kId, u"hey,there" },
+ {}, SourceLine{ "test.xml", 21 },
+ util::make_unique<TestValue>(u"rawValue")));
+
+ EXPECT_FALSE(table.addResource(
+ ResourceNameRef{ u"android", ResourceType::kId, u"hey:there" },
+ {}, SourceLine{ "test.xml", 21 },
+ util::make_unique<TestValue>(u"rawValue")));
+}
+
+TEST(ResourceTableTest, AddOneResource) {
+ const std::u16string kAndroidPackage = u"android";
+
+ ResourceTable table;
+ table.setPackage(kAndroidPackage);
+
+ const ResourceName name = { kAndroidPackage, ResourceType::kAttr, u"id" };
+
+ EXPECT_TRUE(table.addResource(name, {}, SourceLine{ "test/path/file.xml", 23 },
+ util::make_unique<TestValue>(u"rawValue")));
+
+ const ResourceTableType* type;
+ const ResourceEntry* entry;
+ std::tie(type, entry) = table.findResource(name);
+ ASSERT_NE(nullptr, type);
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(name.entry, entry->name);
+
+ ASSERT_NE(std::end(entry->values),
+ std::find_if(std::begin(entry->values), std::end(entry->values),
+ [](const ResourceConfigValue& val) -> bool {
+ return val.config == ConfigDescription{};
+ }));
+}
+
+TEST(ResourceTableTest, AddMultipleResources) {
+ const std::u16string kAndroidPackage = u"android";
+ ResourceTable table;
+ table.setPackage(kAndroidPackage);
+
+ ConfigDescription config;
+ ConfigDescription languageConfig;
+ memcpy(languageConfig.language, "pl", sizeof(languageConfig.language));
+
+ EXPECT_TRUE(table.addResource(
+ ResourceName{ kAndroidPackage, ResourceType::kAttr, u"layout_width" },
+ config, SourceLine{ "test/path/file.xml", 10 },
+ util::make_unique<TestValue>(u"rawValue")));
+
+ EXPECT_TRUE(table.addResource(
+ ResourceName{ kAndroidPackage, ResourceType::kAttr, u"id" },
+ config, SourceLine{ "test/path/file.xml", 12 },
+ util::make_unique<TestValue>(u"rawValue")));
+
+ EXPECT_TRUE(table.addResource(
+ ResourceName{ kAndroidPackage, ResourceType::kString, u"ok" },
+ config, SourceLine{ "test/path/file.xml", 14 },
+ util::make_unique<TestValue>(u"Ok")));
+
+ EXPECT_TRUE(table.addResource(
+ ResourceName{ kAndroidPackage, ResourceType::kString, u"ok" },
+ languageConfig, SourceLine{ "test/path/file.xml", 20 },
+ util::make_unique<TestValue>(u"Tak")));
+
+ const auto endTypeIter = std::end(table);
+ auto typeIter = std::begin(table);
+
+ ASSERT_NE(endTypeIter, typeIter);
+ EXPECT_EQ(ResourceType::kAttr, (*typeIter)->type);
+
+ {
+ const std::unique_ptr<ResourceTableType>& type = *typeIter;
+ const auto endEntryIter = std::end(type->entries);
+ auto entryIter = std::begin(type->entries);
+ ASSERT_NE(endEntryIter, entryIter);
+ EXPECT_EQ(std::u16string(u"id"), (*entryIter)->name);
+
+ ++entryIter;
+ ASSERT_NE(endEntryIter, entryIter);
+ EXPECT_EQ(std::u16string(u"layout_width"), (*entryIter)->name);
+
+ ++entryIter;
+ ASSERT_EQ(endEntryIter, entryIter);
+ }
+
+ ++typeIter;
+ ASSERT_NE(endTypeIter, typeIter);
+ EXPECT_EQ(ResourceType::kString, (*typeIter)->type);
+
+ {
+ const std::unique_ptr<ResourceTableType>& type = *typeIter;
+ const auto endEntryIter = std::end(type->entries);
+ auto entryIter = std::begin(type->entries);
+ ASSERT_NE(endEntryIter, entryIter);
+ EXPECT_EQ(std::u16string(u"ok"), (*entryIter)->name);
+
+ {
+ const std::unique_ptr<ResourceEntry>& entry = *entryIter;
+ const auto endConfigIter = std::end(entry->values);
+ auto configIter = std::begin(entry->values);
+
+ ASSERT_NE(endConfigIter, configIter);
+ EXPECT_EQ(config, configIter->config);
+ const TestValue* value =
+ dynamic_cast<const TestValue*>(configIter->value.get());
+ ASSERT_NE(nullptr, value);
+ EXPECT_EQ(std::u16string(u"Ok"), value->value);
+
+ ++configIter;
+ ASSERT_NE(endConfigIter, configIter);
+ EXPECT_EQ(languageConfig, configIter->config);
+ EXPECT_NE(nullptr, configIter->value);
+
+ value = dynamic_cast<const TestValue*>(configIter->value.get());
+ ASSERT_NE(nullptr, value);
+ EXPECT_EQ(std::u16string(u"Tak"), value->value);
+
+ ++configIter;
+ EXPECT_EQ(endConfigIter, configIter);
+ }
+
+ ++entryIter;
+ ASSERT_EQ(endEntryIter, entryIter);
+ }
+
+ ++typeIter;
+ EXPECT_EQ(endTypeIter, typeIter);
+}
+
+TEST(ResourceTableTest, OverrideWeakResourceValue) {
+ const std::u16string kAndroid = u"android";
+
+ ResourceTable table;
+ table.setPackage(kAndroid);
+ table.setPackageId(0x01);
+
+ ASSERT_TRUE(table.addResource(
+ ResourceName{ kAndroid, ResourceType::kAttr, u"foo" },
+ {}, {}, util::make_unique<TestWeakValue>()));
+
+ const ResourceTableType* type;
+ const ResourceEntry* entry;
+ std::tie(type, entry) = table.findResource(
+ ResourceNameRef{ kAndroid, ResourceType::kAttr, u"foo" });
+ ASSERT_NE(nullptr, type);
+ ASSERT_NE(nullptr, entry);
+ ASSERT_EQ(entry->values.size(), 1u);
+ EXPECT_TRUE(entry->values.front().value->isWeak());
+
+ ASSERT_TRUE(table.addResource(ResourceName{ kAndroid, ResourceType::kAttr, u"foo" }, {}, {},
+ util::make_unique<TestValue>(u"bar")));
+
+ std::tie(type, entry) = table.findResource(
+ ResourceNameRef{ kAndroid, ResourceType::kAttr, u"foo" });
+ ASSERT_NE(nullptr, type);
+ ASSERT_NE(nullptr, entry);
+ ASSERT_EQ(entry->values.size(), 1u);
+ EXPECT_FALSE(entry->values.front().value->isWeak());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResourceTypeExtensions.h b/tools/aapt2/ResourceTypeExtensions.h
new file mode 100644
index 0000000..dcbe923
--- /dev/null
+++ b/tools/aapt2/ResourceTypeExtensions.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_RESOURCE_TYPE_EXTENSIONS_H
+#define AAPT_RESOURCE_TYPE_EXTENSIONS_H
+
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+/**
+ * New android::ResChunk_header types defined
+ * for AAPT to use.
+ *
+ * TODO(adamlesinski): Consider reserving these
+ * enums in androidfw/ResourceTypes.h to avoid
+ * future collisions.
+ */
+enum {
+ RES_TABLE_PUBLIC_TYPE = 0x000d,
+
+ /**
+ * A chunk that holds the string pool
+ * for source entries (path/to/source:line).
+ */
+ RES_TABLE_SOURCE_POOL_TYPE = 0x000e,
+
+ /**
+ * A chunk holding names of externally
+ * defined symbols and offsets to where
+ * they are referenced in the table.
+ */
+ RES_TABLE_SYMBOL_TABLE_TYPE = 0x000f,
+};
+
+/**
+ * New resource types that are meant to only be used
+ * by AAPT and will not end up on the device.
+ */
+struct ExtendedTypes {
+ enum {
+ /**
+ * A raw string value that hasn't had its escape sequences
+ * processed nor whitespace removed.
+ */
+ TYPE_RAW_STRING = 0xfe
+ };
+};
+
+struct Public_header {
+ android::ResChunk_header header;
+
+ /**
+ * The ID of the type this structure refers to.
+ */
+ uint8_t typeId;
+
+ /**
+ * Reserved. Must be 0.
+ */
+ uint8_t res0;
+
+ /**
+ * Reserved. Must be 0.
+ */
+ uint16_t res1;
+
+ /**
+ * Number of public entries.
+ */
+ uint32_t count;
+};
+
+struct Public_entry {
+ uint16_t entryId;
+ uint16_t res0;
+ android::ResStringPool_ref key;
+ android::ResStringPool_ref source;
+ uint32_t sourceLine;
+};
+
+/**
+ * A chunk with type RES_TABLE_SYMBOL_TABLE_TYPE.
+ * Following the header are count number of SymbolTable_entry
+ * structures, followed by an android::ResStringPool_header.
+ */
+struct SymbolTable_header {
+ android::ResChunk_header header;
+
+ /**
+ * Number of SymbolTable_entry structures following
+ * this header.
+ */
+ uint32_t count;
+};
+
+struct SymbolTable_entry {
+ /**
+ * Offset from the beginning of the resource table
+ * where the symbol entry is referenced.
+ */
+ uint32_t offset;
+
+ /**
+ * The index into the string pool where the name of this
+ * symbol exists.
+ */
+ uint32_t stringIndex;
+};
+
+/**
+ * A structure representing the source of a resourc entry.
+ * Appears after an android::ResTable_entry or android::ResTable_map_entry.
+ *
+ * TODO(adamlesinski): This causes some issues when runtime code checks
+ * the size of an android::ResTable_entry. It assumes it is an
+ * android::ResTable_map_entry if the size is bigger than an android::ResTable_entry
+ * which may not be true if this structure is present.
+ */
+struct ResTable_entry_source {
+ /**
+ * Index into the source string pool.
+ */
+ uint32_t pathIndex;
+
+ /**
+ * Line number this resource was defined on.
+ */
+ uint32_t line;
+};
+
+} // namespace aapt
+
+#endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
new file mode 100644
index 0000000..aabb375
--- /dev/null
+++ b/tools/aapt2/ResourceValues.cpp
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Resource.h"
+#include "ResourceTypeExtensions.h"
+#include "ResourceValues.h"
+#include "Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <limits>
+
+namespace aapt {
+
+bool Value::isItem() const {
+ return false;
+}
+
+bool Value::isWeak() const {
+ return false;
+}
+
+bool Item::isItem() const {
+ return true;
+}
+
+RawString::RawString(const StringPool::Ref& ref) : value(ref) {
+}
+
+RawString* RawString::clone(StringPool* newPool) const {
+ return new RawString(newPool->makeRef(*value));
+}
+
+bool RawString::flatten(android::Res_value& outValue) const {
+ outValue.dataType = ExtendedTypes::TYPE_RAW_STRING;
+ outValue.data = static_cast<uint32_t>(value.getIndex());
+ return true;
+}
+
+void RawString::print(std::ostream& out) const {
+ out << "(raw string) " << *value;
+}
+
+Reference::Reference() : referenceType(Reference::Type::kResource) {
+}
+
+Reference::Reference(const ResourceNameRef& n, Type t) :
+ name(n.toResourceName()), referenceType(t) {
+}
+
+Reference::Reference(const ResourceId& i, Type type) : id(i), referenceType(type) {
+}
+
+bool Reference::flatten(android::Res_value& outValue) const {
+ outValue.dataType = (referenceType == Reference::Type::kResource)
+ ? android::Res_value::TYPE_REFERENCE
+ : android::Res_value::TYPE_ATTRIBUTE;
+ outValue.data = id.id;
+ return true;
+}
+
+Reference* Reference::clone(StringPool* /*newPool*/) const {
+ Reference* ref = new Reference();
+ ref->referenceType = referenceType;
+ ref->name = name;
+ ref->id = id;
+ return ref;
+}
+
+void Reference::print(std::ostream& out) const {
+ out << "(reference) ";
+ if (referenceType == Reference::Type::kResource) {
+ out << "@";
+ } else {
+ out << "?";
+ }
+
+ if (name.isValid()) {
+ out << name;
+ }
+
+ if (id.isValid() || Res_INTERNALID(id.id)) {
+ out << " " << id;
+ }
+}
+
+bool Id::isWeak() const {
+ return true;
+}
+
+bool Id::flatten(android::Res_value& out) const {
+ out.dataType = android::Res_value::TYPE_INT_BOOLEAN;
+ out.data = 0;
+ return true;
+}
+
+Id* Id::clone(StringPool* /*newPool*/) const {
+ return new Id();
+}
+
+void Id::print(std::ostream& out) const {
+ out << "(id)";
+}
+
+String::String(const StringPool::Ref& ref) : value(ref) {
+}
+
+bool String::flatten(android::Res_value& outValue) const {
+ // Verify that our StringPool index is within encodeable limits.
+ if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
+ return false;
+ }
+
+ outValue.dataType = android::Res_value::TYPE_STRING;
+ outValue.data = static_cast<uint32_t>(value.getIndex());
+ return true;
+}
+
+String* String::clone(StringPool* newPool) const {
+ return new String(newPool->makeRef(*value));
+}
+
+void String::print(std::ostream& out) const {
+ out << "(string) \"" << *value << "\"";
+}
+
+StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {
+}
+
+bool StyledString::flatten(android::Res_value& outValue) const {
+ if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
+ return false;
+ }
+
+ outValue.dataType = android::Res_value::TYPE_STRING;
+ outValue.data = static_cast<uint32_t>(value.getIndex());
+ return true;
+}
+
+StyledString* StyledString::clone(StringPool* newPool) const {
+ return new StyledString(newPool->makeRef(value));
+}
+
+void StyledString::print(std::ostream& out) const {
+ out << "(styled string) \"" << *value->str << "\"";
+}
+
+FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {
+}
+
+bool FileReference::flatten(android::Res_value& outValue) const {
+ if (path.getIndex() > std::numeric_limits<uint32_t>::max()) {
+ return false;
+ }
+
+ outValue.dataType = android::Res_value::TYPE_STRING;
+ outValue.data = static_cast<uint32_t>(path.getIndex());
+ return true;
+}
+
+FileReference* FileReference::clone(StringPool* newPool) const {
+ return new FileReference(newPool->makeRef(*path));
+}
+
+void FileReference::print(std::ostream& out) const {
+ out << "(file) " << *path;
+}
+
+BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) {
+}
+
+bool BinaryPrimitive::flatten(android::Res_value& outValue) const {
+ outValue = value;
+ return true;
+}
+
+BinaryPrimitive* BinaryPrimitive::clone(StringPool* /*newPool*/) const {
+ return new BinaryPrimitive(value);
+}
+
+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) " << value.data;
+ break;
+ case android::Res_value::TYPE_INT_HEX:
+ out << "(integer) " << 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) : weak(w), typeMask(t) {
+}
+
+bool Attribute::isWeak() const {
+ return weak;
+}
+
+Attribute* Attribute::clone(StringPool* /*newPool*/) const {
+ Attribute* attr = new Attribute(weak);
+ attr->typeMask = typeMask;
+ std::copy(symbols.begin(), symbols.end(), std::back_inserter(attr->symbols));
+ return attr;
+}
+
+void Attribute::printMask(std::ostream& out) const {
+ if (typeMask == android::ResTable_map::TYPE_ANY) {
+ out << "any";
+ return;
+ }
+
+ bool set = false;
+ if ((typeMask & 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 ((typeMask & 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 ((typeMask & 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 ((typeMask & 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 ((typeMask & 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";
+ }
+}
+
+void Attribute::print(std::ostream& out) const {
+ out << "(attr) ";
+ printMask(out);
+
+ out << " ["
+ << util::joiner(symbols.begin(), symbols.end(), ", ")
+ << "]";
+
+ if (weak) {
+ out << " [weak]";
+ }
+}
+
+Style* Style::clone(StringPool* newPool) const {
+ Style* style = new Style();
+ style->parent = parent;
+ style->parentInferred = parentInferred;
+ for (auto& entry : entries) {
+ style->entries.push_back(Entry{
+ entry.key,
+ std::unique_ptr<Item>(entry.value->clone(newPool))
+ });
+ }
+ return style;
+}
+
+void Style::print(std::ostream& out) const {
+ out << "(style) ";
+ if (!parent.name.entry.empty()) {
+ out << parent.name;
+ }
+ out << " ["
+ << util::joiner(entries.begin(), entries.end(), ", ")
+ << "]";
+}
+
+static ::std::ostream& operator<<(::std::ostream& out, const Style::Entry& value) {
+ out << value.key.name << " = ";
+ value.value->print(out);
+ return out;
+}
+
+Array* Array::clone(StringPool* newPool) const {
+ Array* array = new Array();
+ for (auto& item : items) {
+ array->items.emplace_back(std::unique_ptr<Item>(item->clone(newPool)));
+ }
+ return array;
+}
+
+void Array::print(std::ostream& out) const {
+ out << "(array) ["
+ << util::joiner(items.begin(), items.end(), ", ")
+ << "]";
+}
+
+Plural* Plural::clone(StringPool* newPool) const {
+ Plural* p = new Plural();
+ 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;
+}
+
+void Plural::print(std::ostream& out) const {
+ out << "(plural)";
+}
+
+static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Item>& item) {
+ return out << *item;
+}
+
+Styleable* Styleable::clone(StringPool* /*newPool*/) const {
+ Styleable* styleable = new Styleable();
+ std::copy(entries.begin(), entries.end(), std::back_inserter(styleable->entries));
+ return styleable;
+}
+
+void Styleable::print(std::ostream& out) const {
+ out << "(styleable) " << " ["
+ << util::joiner(entries.begin(), entries.end(), ", ")
+ << "]";
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
new file mode 100644
index 0000000..ef6594e
--- /dev/null
+++ b/tools/aapt2/ResourceValues.h
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_RESOURCE_VALUES_H
+#define AAPT_RESOURCE_VALUES_H
+
+#include "Resource.h"
+#include "StringPool.h"
+
+#include <array>
+#include <androidfw/ResourceTypes.h>
+#include <ostream>
+#include <vector>
+
+namespace aapt {
+
+struct ValueVisitor;
+struct ConstValueVisitor;
+struct ValueVisitorArgs;
+
+/**
+ * A resource value. This is an all-encompassing representation
+ * of Item and Map and their subclasses. The way to do
+ * type specific operations is to check the Value's type() and
+ * cast it to the appropriate subclass. This isn't super clean,
+ * but it is the simplest strategy.
+ */
+struct Value {
+ /**
+ * Whether or not this is an Item.
+ */
+ virtual bool isItem() const;
+
+ /**
+ * Whether this value is weak and can be overriden without
+ * warning or error. Default for base class is false.
+ */
+ virtual bool isWeak() const;
+
+ /**
+ * Calls the appropriate overload of ValueVisitor.
+ */
+ virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) = 0;
+
+ /**
+ * Const version of accept().
+ */
+ virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const = 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;
+};
+
+/**
+ * Inherit from this to get visitor accepting implementations for free.
+ */
+template <typename Derived>
+struct BaseValue : public Value {
+ virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) override;
+ virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const override;
+};
+
+/**
+ * A resource item with a single value. This maps to android::ResTable_entry.
+ */
+struct Item : public Value {
+ /**
+ * An Item is, of course, an Item.
+ */
+ virtual bool isItem() const override;
+
+ /**
+ * Clone the Item.
+ */
+ virtual Item* clone(StringPool* newPool) const override = 0;
+
+ /**
+ * Fills in an android::Res_value structure with this Item's binary representation.
+ * Returns false if an error ocurred.
+ */
+ virtual bool flatten(android::Res_value& outValue) const = 0;
+};
+
+/**
+ * Inherit from this to get visitor accepting implementations for free.
+ */
+template <typename Derived>
+struct BaseItem : public Item {
+ virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) override;
+ virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const override;
+};
+
+/**
+ * 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
+ * numeric (the id is set to a valid resource ID).
+ */
+struct Reference : public BaseItem<Reference> {
+ enum class Type {
+ kResource,
+ kAttribute,
+ };
+
+ ResourceName name;
+ ResourceId id;
+ Reference::Type referenceType;
+ bool privateReference = false;
+
+ Reference();
+ Reference(const ResourceNameRef& n, Type type = Type::kResource);
+ Reference(const ResourceId& i, Type type = Type::kResource);
+
+ bool flatten(android::Res_value& outValue) const override;
+ Reference* clone(StringPool* newPool) const override;
+ void print(std::ostream& out) const override;
+};
+
+/**
+ * An ID resource. Has no real value, just a place holder.
+ */
+struct Id : public BaseItem<Id> {
+ bool isWeak() const override;
+ bool flatten(android::Res_value& out) const override;
+ Id* clone(StringPool* newPool) const override;
+ void print(std::ostream& out) const override;
+};
+
+/**
+ * A raw, unprocessed string. This may contain quotations,
+ * escape sequences, and whitespace. This shall *NOT*
+ * end up in the final resource table.
+ */
+struct RawString : public BaseItem<RawString> {
+ StringPool::Ref value;
+
+ RawString(const StringPool::Ref& ref);
+
+ bool flatten(android::Res_value& outValue) const override;
+ RawString* clone(StringPool* newPool) const override;
+ void print(std::ostream& out) const override;
+};
+
+struct String : public BaseItem<String> {
+ StringPool::Ref value;
+
+ String(const StringPool::Ref& ref);
+
+ bool flatten(android::Res_value& outValue) const override;
+ String* clone(StringPool* newPool) const override;
+ void print(std::ostream& out) const override;
+};
+
+struct StyledString : public BaseItem<StyledString> {
+ StringPool::StyleRef value;
+
+ StyledString(const StringPool::StyleRef& ref);
+
+ bool flatten(android::Res_value& outValue) const override;
+ StyledString* clone(StringPool* newPool) const override;
+ void print(std::ostream& out) const override;
+};
+
+struct FileReference : public BaseItem<FileReference> {
+ StringPool::Ref path;
+
+ FileReference() = default;
+ FileReference(const StringPool::Ref& path);
+
+ bool flatten(android::Res_value& outValue) const override;
+ FileReference* clone(StringPool* newPool) const override;
+ void print(std::ostream& out) const override;
+};
+
+/**
+ * Represents any other android::Res_value.
+ */
+struct BinaryPrimitive : public BaseItem<BinaryPrimitive> {
+ android::Res_value value;
+
+ BinaryPrimitive() = default;
+ BinaryPrimitive(const android::Res_value& val);
+
+ bool flatten(android::Res_value& outValue) const override;
+ BinaryPrimitive* clone(StringPool* newPool) const override;
+ void print(std::ostream& out) const override;
+};
+
+struct Attribute : public BaseValue<Attribute> {
+ struct Symbol {
+ Reference symbol;
+ uint32_t value;
+ };
+
+ bool weak;
+ uint32_t typeMask;
+ uint32_t minInt;
+ uint32_t maxInt;
+ std::vector<Symbol> symbols;
+
+ Attribute(bool w, uint32_t t = 0u);
+
+ bool isWeak() const override;
+ virtual Attribute* clone(StringPool* newPool) const override;
+ void printMask(std::ostream& out) const;
+ virtual void print(std::ostream& out) const override;
+};
+
+struct Style : public BaseValue<Style> {
+ struct Entry {
+ Reference key;
+ std::unique_ptr<Item> value;
+ };
+
+ Reference parent;
+
+ /**
+ * If set to true, the parent was auto inferred from the
+ * style's name.
+ */
+ bool parentInferred = false;
+
+ std::vector<Entry> entries;
+
+ Style* clone(StringPool* newPool) const override;
+ void print(std::ostream& out) const override;
+};
+
+struct Array : public BaseValue<Array> {
+ std::vector<std::unique_ptr<Item>> items;
+
+ Array* clone(StringPool* newPool) const override;
+ void print(std::ostream& out) const override;
+};
+
+struct Plural : public BaseValue<Plural> {
+ enum {
+ Zero = 0,
+ One,
+ Two,
+ Few,
+ Many,
+ Other,
+ Count
+ };
+
+ std::array<std::unique_ptr<Item>, Count> values;
+
+ Plural* clone(StringPool* newPool) const override;
+ void print(std::ostream& out) const override;
+};
+
+struct Styleable : public BaseValue<Styleable> {
+ std::vector<Reference> entries;
+
+ Styleable* clone(StringPool* newPool) const override;
+ void print(std::ostream& out) const override;
+};
+
+/**
+ * Stream operator for printing Value objects.
+ */
+inline ::std::ostream& operator<<(::std::ostream& out, const Value& value) {
+ value.print(out);
+ return out;
+}
+
+inline ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol& s) {
+ return out << s.symbol.name.entry << "=" << s.value;
+}
+
+/**
+ * The argument object that gets passed through the value
+ * back to the ValueVisitor. Subclasses of ValueVisitor should
+ * subclass ValueVisitorArgs to contain the data they need
+ * to operate.
+ */
+struct ValueVisitorArgs {};
+
+/**
+ * Visits a value and runs the appropriate method based on its type.
+ */
+struct ValueVisitor {
+ virtual void visit(Reference& reference, ValueVisitorArgs& args) {
+ visitItem(reference, args);
+ }
+
+ virtual void visit(RawString& string, ValueVisitorArgs& args) {
+ visitItem(string, args);
+ }
+
+ virtual void visit(String& string, ValueVisitorArgs& args) {
+ visitItem(string, args);
+ }
+
+ virtual void visit(StyledString& string, ValueVisitorArgs& args) {
+ visitItem(string, args);
+ }
+
+ virtual void visit(FileReference& file, ValueVisitorArgs& args) {
+ visitItem(file, args);
+ }
+
+ virtual void visit(Id& id, ValueVisitorArgs& args) {
+ visitItem(id, args);
+ }
+
+ virtual void visit(BinaryPrimitive& primitive, ValueVisitorArgs& args) {
+ visitItem(primitive, args);
+ }
+
+ virtual void visit(Attribute& attr, ValueVisitorArgs& args) {}
+ virtual void visit(Style& style, ValueVisitorArgs& args) {}
+ virtual void visit(Array& array, ValueVisitorArgs& args) {}
+ virtual void visit(Plural& array, ValueVisitorArgs& args) {}
+ virtual void visit(Styleable& styleable, ValueVisitorArgs& args) {}
+
+ virtual void visitItem(Item& item, ValueVisitorArgs& args) {}
+};
+
+/**
+ * Const version of ValueVisitor.
+ */
+struct ConstValueVisitor {
+ virtual void visit(const Reference& reference, ValueVisitorArgs& args) {
+ visitItem(reference, args);
+ }
+
+ virtual void visit(const RawString& string, ValueVisitorArgs& args) {
+ visitItem(string, args);
+ }
+
+ virtual void visit(const String& string, ValueVisitorArgs& args) {
+ visitItem(string, args);
+ }
+
+ virtual void visit(const StyledString& string, ValueVisitorArgs& args) {
+ visitItem(string, args);
+ }
+
+ virtual void visit(const FileReference& file, ValueVisitorArgs& args) {
+ visitItem(file, args);
+ }
+
+ virtual void visit(const Id& id, ValueVisitorArgs& args) {
+ visitItem(id, args);
+ }
+
+ virtual void visit(const BinaryPrimitive& primitive, ValueVisitorArgs& args) {
+ visitItem(primitive, args);
+ }
+
+ virtual void visit(const Attribute& attr, ValueVisitorArgs& args) {}
+ virtual void visit(const Style& style, ValueVisitorArgs& args) {}
+ virtual void visit(const Array& array, ValueVisitorArgs& args) {}
+ virtual void visit(const Plural& array, ValueVisitorArgs& args) {}
+ virtual void visit(const Styleable& styleable, ValueVisitorArgs& args) {}
+
+ virtual void visitItem(const Item& item, ValueVisitorArgs& args) {}
+};
+
+/**
+ * Convenience Visitor that forwards a specific type to a function.
+ * Args are not used as the function can bind variables. Do not use
+ * directly, use the wrapper visitFunc() method.
+ */
+template <typename T, typename TFunc>
+struct ValueVisitorFunc : ValueVisitor {
+ TFunc func;
+
+ ValueVisitorFunc(TFunc f) : func(f) {
+ }
+
+ void visit(T& value, ValueVisitorArgs&) override {
+ func(value);
+ }
+};
+
+/**
+ * Const version of ValueVisitorFunc.
+ */
+template <typename T, typename TFunc>
+struct ConstValueVisitorFunc : ConstValueVisitor {
+ TFunc func;
+
+ ConstValueVisitorFunc(TFunc f) : func(f) {
+ }
+
+ void visit(const T& value, ValueVisitorArgs&) override {
+ func(value);
+ }
+};
+
+template <typename T, typename TFunc>
+void visitFunc(Value& value, TFunc f) {
+ ValueVisitorFunc<T, TFunc> visitor(f);
+ value.accept(visitor, ValueVisitorArgs{});
+}
+
+template <typename T, typename TFunc>
+void visitFunc(const Value& value, TFunc f) {
+ ConstValueVisitorFunc<T, TFunc> visitor(f);
+ value.accept(visitor, ValueVisitorArgs{});
+}
+
+template <typename Derived>
+void BaseValue<Derived>::accept(ValueVisitor& visitor, ValueVisitorArgs&& args) {
+ visitor.visit(static_cast<Derived&>(*this), args);
+}
+
+template <typename Derived>
+void BaseValue<Derived>::accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const {
+ visitor.visit(static_cast<const Derived&>(*this), args);
+}
+
+template <typename Derived>
+void BaseItem<Derived>::accept(ValueVisitor& visitor, ValueVisitorArgs&& args) {
+ visitor.visit(static_cast<Derived&>(*this), args);
+}
+
+template <typename Derived>
+void BaseItem<Derived>::accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const {
+ visitor.visit(static_cast<const Derived&>(*this), args);
+}
+
+} // namespace aapt
+
+#endif // AAPT_RESOURCE_VALUES_H
diff --git a/tools/aapt2/Resource_test.cpp b/tools/aapt2/Resource_test.cpp
new file mode 100644
index 0000000..d957999
--- /dev/null
+++ b/tools/aapt2/Resource_test.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "Resource.h"
+
+namespace aapt {
+
+TEST(ResourceTypeTest, ParseResourceTypes) {
+ const ResourceType* type = parseResourceType(u"anim");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kAnim);
+
+ type = parseResourceType(u"animator");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kAnimator);
+
+ type = parseResourceType(u"array");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kArray);
+
+ type = parseResourceType(u"attr");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kAttr);
+
+ type = parseResourceType(u"^attr-private");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kAttrPrivate);
+
+ type = parseResourceType(u"bool");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kBool);
+
+ type = parseResourceType(u"color");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kColor);
+
+ type = parseResourceType(u"dimen");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kDimen);
+
+ type = parseResourceType(u"drawable");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kDrawable);
+
+ type = parseResourceType(u"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(u"integer");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kInteger);
+
+ type = parseResourceType(u"integer-array");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kIntegerArray);
+
+ type = parseResourceType(u"interpolator");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kInterpolator);
+
+ type = parseResourceType(u"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(u"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(u"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(u"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(u"xml");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kXml);
+
+ type = parseResourceType(u"blahaha");
+ EXPECT_EQ(type, nullptr);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ScopedXmlPullParser.cpp b/tools/aapt2/ScopedXmlPullParser.cpp
new file mode 100644
index 0000000..48da93e
--- /dev/null
+++ b/tools/aapt2/ScopedXmlPullParser.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ScopedXmlPullParser.h"
+
+#include <string>
+
+namespace aapt {
+
+ScopedXmlPullParser::ScopedXmlPullParser(XmlPullParser* parser) :
+ mParser(parser), mDepth(parser->getDepth()), mDone(false) {
+}
+
+ScopedXmlPullParser::~ScopedXmlPullParser() {
+ while (isGoodEvent(next()));
+}
+
+XmlPullParser::Event ScopedXmlPullParser::next() {
+ if (mDone) {
+ return Event::kEndDocument;
+ }
+
+ const Event event = mParser->next();
+ if (mParser->getDepth() <= mDepth) {
+ mDone = true;
+ }
+ return event;
+}
+
+XmlPullParser::Event ScopedXmlPullParser::getEvent() const {
+ return mParser->getEvent();
+}
+
+const std::string& ScopedXmlPullParser::getLastError() const {
+ return mParser->getLastError();
+}
+
+const std::u16string& ScopedXmlPullParser::getComment() const {
+ return mParser->getComment();
+}
+
+size_t ScopedXmlPullParser::getLineNumber() const {
+ return mParser->getLineNumber();
+}
+
+size_t ScopedXmlPullParser::getDepth() const {
+ const size_t depth = mParser->getDepth();
+ if (depth < mDepth) {
+ return 0;
+ }
+ return depth - mDepth;
+}
+
+const std::u16string& ScopedXmlPullParser::getText() const {
+ return mParser->getText();
+}
+
+const std::u16string& ScopedXmlPullParser::getNamespacePrefix() const {
+ return mParser->getNamespacePrefix();
+}
+
+const std::u16string& ScopedXmlPullParser::getNamespaceUri() const {
+ return mParser->getNamespaceUri();
+}
+
+bool ScopedXmlPullParser::applyPackageAlias(std::u16string* package,
+ const std::u16string& defaultPackage) const {
+ return mParser->applyPackageAlias(package, defaultPackage);
+}
+
+const std::u16string& ScopedXmlPullParser::getElementNamespace() const {
+ return mParser->getElementNamespace();
+}
+
+const std::u16string& ScopedXmlPullParser::getElementName() const {
+ return mParser->getElementName();
+}
+
+size_t ScopedXmlPullParser::getAttributeCount() const {
+ return mParser->getAttributeCount();
+}
+
+XmlPullParser::const_iterator ScopedXmlPullParser::beginAttributes() const {
+ return mParser->beginAttributes();
+}
+
+XmlPullParser::const_iterator ScopedXmlPullParser::endAttributes() const {
+ return mParser->endAttributes();
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ScopedXmlPullParser.h b/tools/aapt2/ScopedXmlPullParser.h
new file mode 100644
index 0000000..a040f60
--- /dev/null
+++ b/tools/aapt2/ScopedXmlPullParser.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_SCOPED_XML_PULL_PARSER_H
+#define AAPT_SCOPED_XML_PULL_PARSER_H
+
+#include "XmlPullParser.h"
+
+#include <string>
+
+namespace aapt {
+
+/**
+ * An XmlPullParser that will not read past the depth
+ * of the underlying parser. When this parser is destroyed,
+ * it moves the underlying parser to the same depth it
+ * started with.
+ *
+ * You can write code like this:
+ *
+ * while (XmlPullParser::isGoodEvent(parser.next())) {
+ * if (parser.getEvent() != XmlPullParser::Event::StartElement) {
+ * continue;
+ * }
+ *
+ * ScopedXmlPullParser scoped(parser);
+ * if (parser.getElementName() == u"id") {
+ * // do work.
+ * } else {
+ * // do nothing, as all the sub elements will be skipped
+ * // when scoped goes out of scope.
+ * }
+ * }
+ */
+class ScopedXmlPullParser : public XmlPullParser {
+public:
+ ScopedXmlPullParser(XmlPullParser* parser);
+ ScopedXmlPullParser(const ScopedXmlPullParser&) = delete;
+ ScopedXmlPullParser& operator=(const ScopedXmlPullParser&) = delete;
+ ~ScopedXmlPullParser();
+
+ Event getEvent() const override;
+ const std::string& getLastError() const override;
+ Event next() override;
+
+ const std::u16string& getComment() const override;
+ size_t getLineNumber() const override;
+ size_t getDepth() const override;
+
+ const std::u16string& getText() const override;
+
+ const std::u16string& getNamespacePrefix() const override;
+ const std::u16string& getNamespaceUri() const override;
+ bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage)
+ const override;
+
+ const std::u16string& getElementNamespace() const override;
+ const std::u16string& getElementName() const override;
+
+ const_iterator beginAttributes() const override;
+ const_iterator endAttributes() const override;
+ size_t getAttributeCount() const override;
+
+private:
+ XmlPullParser* mParser;
+ size_t mDepth;
+ bool mDone;
+};
+
+} // namespace aapt
+
+#endif // AAPT_SCOPED_XML_PULL_PARSER_H
diff --git a/tools/aapt2/ScopedXmlPullParser_test.cpp b/tools/aapt2/ScopedXmlPullParser_test.cpp
new file mode 100644
index 0000000..342f305
--- /dev/null
+++ b/tools/aapt2/ScopedXmlPullParser_test.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ScopedXmlPullParser.h"
+#include "SourceXmlPullParser.h"
+
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+TEST(ScopedXmlPullParserTest, StopIteratingAtNoNZeroDepth) {
+ std::stringstream input;
+ input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
+ << "<resources><string></string></resources>" << std::endl;
+
+ SourceXmlPullParser sourceParser(input);
+ EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
+ EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
+
+ EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
+ EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
+
+ {
+ ScopedXmlPullParser scopedParser(&sourceParser);
+ EXPECT_EQ(XmlPullParser::Event::kEndElement, scopedParser.next());
+ EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
+
+ EXPECT_EQ(XmlPullParser::Event::kEndDocument, scopedParser.next());
+ }
+
+ EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next());
+ EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
+
+ EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next());
+}
+
+TEST(ScopedXmlPullParserTest, FinishCurrentElementOnDestruction) {
+ std::stringstream input;
+ input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
+ << "<resources><string></string></resources>" << std::endl;
+
+ SourceXmlPullParser sourceParser(input);
+ EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
+ EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
+
+ EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
+ EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
+
+ {
+ ScopedXmlPullParser scopedParser(&sourceParser);
+ EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
+ }
+
+ EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next());
+ EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
+
+ EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next());
+}
+
+TEST(ScopedXmlPullParserTest, NestedParsersOperateCorrectly) {
+ std::stringstream input;
+ input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
+ << "<resources><string><foo></foo></string></resources>" << std::endl;
+
+ SourceXmlPullParser sourceParser(input);
+ EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
+ EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
+
+ EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
+ EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
+
+ {
+ ScopedXmlPullParser scopedParser(&sourceParser);
+ EXPECT_EQ(std::u16string(u"string"), scopedParser.getElementName());
+ while (XmlPullParser::isGoodEvent(scopedParser.next())) {
+ if (scopedParser.getEvent() != XmlPullParser::Event::kStartElement) {
+ continue;
+ }
+
+ ScopedXmlPullParser subScopedParser(&scopedParser);
+ EXPECT_EQ(std::u16string(u"foo"), subScopedParser.getElementName());
+ }
+ }
+
+ EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next());
+ EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
+
+ EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
new file mode 100644
index 0000000..9bdae49
--- /dev/null
+++ b/tools/aapt2/SdkConstants.cpp
@@ -0,0 +1,737 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SdkConstants.h"
+
+#include <algorithm>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace aapt {
+
+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 },
+ { 0x0366, SDK_HONEYCOMB_MR1 },
+ { 0x03a6, SDK_HONEYCOMB_MR2 },
+ { 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;
+}
+
+size_t findAttributeSdkLevel(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;
+}
+
+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 }
+};
+
+size_t findAttributeSdkLevel(const ResourceName& name) {
+ if (name.package != u"android" && name.type != ResourceType::kAttr) {
+ return 0;
+ }
+
+ auto iter = sAttrMap.find(name.entry);
+ if (iter != sAttrMap.end()) {
+ return iter->second;
+ }
+ return SDK_LOLLIPOP_MR1;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
new file mode 100644
index 0000000..803da03
--- /dev/null
+++ b/tools/aapt2/SdkConstants.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_SDK_CONSTANTS_H
+#define AAPT_SDK_CONSTANTS_H
+
+#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,
+};
+
+size_t findAttributeSdkLevel(ResourceId id);
+size_t findAttributeSdkLevel(const ResourceName& name);
+
+} // namespace aapt
+
+#endif // AAPT_SDK_CONSTANTS_H
diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h
new file mode 100644
index 0000000..3606488
--- /dev/null
+++ b/tools/aapt2/Source.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_SOURCE_H
+#define AAPT_SOURCE_H
+
+#include <ostream>
+#include <string>
+#include <tuple>
+
+namespace aapt {
+
+struct SourceLineColumn;
+struct SourceLine;
+
+/**
+ * Represents a file on disk. Used for logging and
+ * showing errors.
+ */
+struct Source {
+ std::string path;
+
+ inline SourceLine line(size_t line) const;
+};
+
+/**
+ * Represents a file on disk and a line number in that file.
+ * Used for logging and showing errors.
+ */
+struct SourceLine {
+ std::string path;
+ size_t line;
+
+ inline SourceLineColumn column(size_t column) const;
+};
+
+/**
+ * Represents a file on disk and a line:column number in that file.
+ * Used for logging and showing errors.
+ */
+struct SourceLineColumn {
+ std::string path;
+ size_t line;
+ size_t column;
+};
+
+//
+// Implementations
+//
+
+SourceLine Source::line(size_t line) const {
+ return SourceLine{ path, line };
+}
+
+SourceLineColumn SourceLine::column(size_t column) const {
+ return SourceLineColumn{ path, line, column };
+}
+
+inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) {
+ return out << source.path;
+}
+
+inline ::std::ostream& operator<<(::std::ostream& out, const SourceLine& source) {
+ return out << source.path << ":" << source.line;
+}
+
+inline ::std::ostream& operator<<(::std::ostream& out, const SourceLineColumn& source) {
+ return out << source.path << ":" << source.line << ":" << source.column;
+}
+
+inline bool operator<(const SourceLine& lhs, const SourceLine& rhs) {
+ return std::tie(lhs.path, lhs.line) < std::tie(rhs.path, rhs.line);
+}
+
+} // namespace aapt
+
+#endif // AAPT_SOURCE_H
diff --git a/tools/aapt2/SourceXmlPullParser.cpp b/tools/aapt2/SourceXmlPullParser.cpp
new file mode 100644
index 0000000..8099044
--- /dev/null
+++ b/tools/aapt2/SourceXmlPullParser.cpp
@@ -0,0 +1,283 @@
+/*
+ * 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 <iostream>
+#include <string>
+
+#include "Maybe.h"
+#include "SourceXmlPullParser.h"
+#include "Util.h"
+
+namespace aapt {
+
+constexpr char kXmlNamespaceSep = 1;
+
+SourceXmlPullParser::SourceXmlPullParser(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++ });
+}
+
+SourceXmlPullParser::~SourceXmlPullParser() {
+ XML_ParserFree(mParser);
+}
+
+SourceXmlPullParser::Event SourceXmlPullParser::next() {
+ const Event currentEvent = getEvent();
+ if (currentEvent == Event::kBadDocument || currentEvent == Event::kEndDocument) {
+ return currentEvent;
+ }
+
+ 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 });
+ }
+ }
+
+ 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<std::u16string> result = util::extractPackageFromNamespace(getNamespaceUri());
+ if (event == Event::kStartNamespace) {
+ if (result) {
+ mPackageAliases.emplace_back(getNamespacePrefix(), result.value());
+ }
+ } else {
+ if (result) {
+ assert(mPackageAliases.back().second == result.value());
+ mPackageAliases.pop_back();
+ }
+ }
+ }
+
+ return event;
+}
+
+SourceXmlPullParser::Event SourceXmlPullParser::getEvent() const {
+ return mEventQueue.front().event;
+}
+
+const std::string& SourceXmlPullParser::getLastError() const {
+ return mLastError;
+}
+
+const std::u16string& SourceXmlPullParser::getComment() const {
+ return mEventQueue.front().comment;
+}
+
+size_t SourceXmlPullParser::getLineNumber() const {
+ return mEventQueue.front().lineNumber;
+}
+
+size_t SourceXmlPullParser::getDepth() const {
+ return mEventQueue.front().depth;
+}
+
+const std::u16string& SourceXmlPullParser::getText() const {
+ if (getEvent() != Event::kText) {
+ return mEmpty;
+ }
+ return mEventQueue.front().data1;
+}
+
+const std::u16string& SourceXmlPullParser::getNamespacePrefix() const {
+ const Event currentEvent = getEvent();
+ if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) {
+ return mEmpty;
+ }
+ return mEventQueue.front().data1;
+}
+
+const std::u16string& SourceXmlPullParser::getNamespaceUri() const {
+ const Event currentEvent = getEvent();
+ if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) {
+ return mEmpty;
+ }
+ return mEventQueue.front().data2;
+}
+
+bool SourceXmlPullParser::applyPackageAlias(std::u16string* package,
+ const std::u16string& defaultPackage) const {
+ const auto endIter = mPackageAliases.rend();
+ for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
+ if (iter->first == *package) {
+ if (iter->second.empty()) {
+ *package = defaultPackage;
+ } else {
+ *package = iter->second;
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+const std::u16string& SourceXmlPullParser::getElementNamespace() const {
+ const Event currentEvent = getEvent();
+ if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) {
+ return mEmpty;
+ }
+ return mEventQueue.front().data1;
+}
+
+const std::u16string& SourceXmlPullParser::getElementName() const {
+ const Event currentEvent = getEvent();
+ if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) {
+ return mEmpty;
+ }
+ return mEventQueue.front().data2;
+}
+
+XmlPullParser::const_iterator SourceXmlPullParser::beginAttributes() const {
+ return mEventQueue.front().attributes.begin();
+}
+
+XmlPullParser::const_iterator SourceXmlPullParser::endAttributes() const {
+ return mEventQueue.front().attributes.end();
+}
+
+size_t SourceXmlPullParser::getAttributeCount() const {
+ if (getEvent() != Event::kStartElement) {
+ return 0;
+ }
+ return mEventQueue.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);
+ }
+}
+
+void XMLCALL SourceXmlPullParser::startNamespaceHandler(void* userData, const char* prefix,
+ const char* uri) {
+ SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(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 SourceXmlPullParser::startElementHandler(void* userData, const char* name,
+ const char** attrs) {
+ SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(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 SourceXmlPullParser::characterDataHandler(void* userData, const char* s, int len) {
+ SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+
+ parser->mEventQueue.push(EventData{
+ Event::kText,
+ XML_GetCurrentLineNumber(parser->mParser),
+ parser->mDepth,
+ util::utf8ToUtf16(StringPiece(s, len))
+ });
+}
+
+void XMLCALL SourceXmlPullParser::endElementHandler(void* userData, const char* name) {
+ SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+
+ EventData data = {
+ Event::kEndElement, XML_GetCurrentLineNumber(parser->mParser), --(parser->mDepth)
+ };
+ splitName(name, data.data1, data.data2);
+
+ // Move the data into the queue (no copy).
+ parser->mEventQueue.push(std::move(data));
+}
+
+void XMLCALL SourceXmlPullParser::endNamespaceHandler(void* userData, const char* prefix) {
+ SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(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 SourceXmlPullParser::commentDataHandler(void* userData, const char* comment) {
+ SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+
+ parser->mEventQueue.push(EventData{
+ Event::kComment,
+ XML_GetCurrentLineNumber(parser->mParser),
+ parser->mDepth,
+ util::utf8ToUtf16(comment)
+ });
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/SourceXmlPullParser.h b/tools/aapt2/SourceXmlPullParser.h
new file mode 100644
index 0000000..15936d6
--- /dev/null
+++ b/tools/aapt2/SourceXmlPullParser.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_SOURCE_XML_PULL_PARSER_H
+#define AAPT_SOURCE_XML_PULL_PARSER_H
+
+#include "XmlPullParser.h"
+
+#include <istream>
+#include <libexpat/expat.h>
+#include <queue>
+#include <stack>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+class SourceXmlPullParser : public XmlPullParser {
+public:
+ SourceXmlPullParser(std::istream& in);
+ SourceXmlPullParser(const SourceXmlPullParser& rhs) = delete;
+ ~SourceXmlPullParser();
+
+ Event getEvent() const override;
+ const std::string& getLastError() const override ;
+ Event next() override ;
+
+ const std::u16string& getComment() const override;
+ size_t getLineNumber() const override;
+ size_t getDepth() const override;
+
+ const std::u16string& getText() const override;
+
+ const std::u16string& getNamespacePrefix() const override;
+ const std::u16string& getNamespaceUri() const override;
+ bool applyPackageAlias(std::u16string* package,
+ const std::u16string& defaultPackage) const override;
+
+
+ const std::u16string& getElementNamespace() const override;
+ const std::u16string& getElementName() const override;
+
+ const_iterator beginAttributes() const override;
+ const_iterator endAttributes() const override;
+ size_t getAttributeCount() const override;
+
+private:
+ static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri);
+ static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs);
+ static void XMLCALL characterDataHandler(void* userData, const char* s, int len);
+ static void XMLCALL endElementHandler(void* userData, const char* name);
+ static void XMLCALL endNamespaceHandler(void* userData, const char* prefix);
+ static void XMLCALL commentDataHandler(void* userData, const char* comment);
+
+ struct EventData {
+ Event event;
+ size_t lineNumber;
+ size_t depth;
+ std::u16string data1;
+ std::u16string data2;
+ std::u16string comment;
+ std::vector<Attribute> attributes;
+ };
+
+ std::istream& mIn;
+ XML_Parser mParser;
+ char mBuffer[16384];
+ std::queue<EventData> mEventQueue;
+ std::string mLastError;
+ const std::u16string mEmpty;
+ size_t mDepth;
+ std::stack<std::u16string> mNamespaceUris;
+ std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
+};
+
+} // namespace aapt
+
+#endif // AAPT_SOURCE_XML_PULL_PARSER_H
diff --git a/tools/aapt2/StringPiece.h b/tools/aapt2/StringPiece.h
new file mode 100644
index 0000000..e2a1597
--- /dev/null
+++ b/tools/aapt2/StringPiece.h
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_STRING_PIECE_H
+#define AAPT_STRING_PIECE_H
+
+#include <ostream>
+#include <string>
+#include <utils/String8.h>
+#include <utils/Unicode.h>
+
+namespace aapt {
+
+/**
+ * Read only wrapper around basic C strings.
+ * Prevents excessive copying.
+ *
+ * WARNING: When creating from std::basic_string<>, moving the original
+ * std::basic_string<> will invalidate the data held in a BasicStringPiece<>.
+ * BasicStringPiece<> should only be used transitively.
+ */
+template <typename TChar>
+class BasicStringPiece {
+public:
+ using const_iterator = const TChar*;
+
+ 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;
+
+ 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;
+};
+
+using StringPiece = BasicStringPiece<char>;
+using StringPiece16 = BasicStringPiece<char16_t>;
+
+//
+// BasicStringPiece implementation.
+//
+
+template <typename TChar>
+inline BasicStringPiece<TChar>::BasicStringPiece() : mData(nullptr) , mLength(0) {
+}
+
+template <typename TChar>
+inline BasicStringPiece<TChar>::BasicStringPiece(const BasicStringPiece<TChar>& str) :
+ mData(str.mData), mLength(str.mLength) {
+}
+
+template <typename TChar>
+inline BasicStringPiece<TChar>::BasicStringPiece(const std::basic_string<TChar>& str) :
+ mData(str.data()), mLength(str.length()) {
+}
+
+template <>
+inline BasicStringPiece<char>::BasicStringPiece(const char* str) :
+ mData(str), mLength(str != nullptr ? strlen(str) : 0) {
+}
+
+template <>
+inline BasicStringPiece<char16_t>::BasicStringPiece(const char16_t* str) :
+ mData(str), mLength(str != nullptr ? strlen16(str) : 0) {
+}
+
+template <typename TChar>
+inline BasicStringPiece<TChar>::BasicStringPiece(const TChar* str, size_t len) :
+ mData(str), mLength(len) {
+}
+
+template <typename TChar>
+inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::operator=(
+ const BasicStringPiece<TChar>& rhs) {
+ mData = rhs.mData;
+ mLength = rhs.mLength;
+ return *this;
+}
+
+template <typename TChar>
+inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign(const TChar* str, size_t len) {
+ mData = str;
+ mLength = 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);
+}
+
+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);
+}
+
+template <typename TChar>
+inline const TChar* BasicStringPiece<TChar>::data() const {
+ return mData;
+}
+
+template <typename TChar>
+inline size_t BasicStringPiece<TChar>::length() const {
+ return mLength;
+}
+
+template <typename TChar>
+inline size_t BasicStringPiece<TChar>::size() const {
+ return mLength;
+}
+
+template <typename TChar>
+inline bool BasicStringPiece<TChar>::empty() const {
+ return mLength == 0;
+}
+
+template <typename TChar>
+inline std::basic_string<TChar> BasicStringPiece<TChar>::toString() const {
+ return std::basic_string<TChar>(mData, mLength);
+}
+
+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;
+ }
+ }
+ return static_cast<int>(mLength - rhs.mLength);
+}
+
+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 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);
+}
+
+template <typename TChar>
+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;
+}
+
+template <typename TChar>
+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;
+}
+
+template <typename TChar>
+inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::begin() const {
+ return mData;
+}
+
+template <typename TChar>
+inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::end() const {
+ return mData + mLength;
+}
+
+inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char>& str) {
+ return out.write(str.data(), str.size());
+}
+
+} // namespace aapt
+
+#endif // AAPT_STRING_PIECE_H
diff --git a/tools/aapt2/StringPiece_test.cpp b/tools/aapt2/StringPiece_test.cpp
new file mode 100644
index 0000000..43f7a37
--- /dev/null
+++ b/tools/aapt2/StringPiece_test.cpp
@@ -0,0 +1,62 @@
+/*
+ * 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 <algorithm>
+#include <gtest/gtest.h>
+#include <string>
+#include <vector>
+
+#include "StringPiece.h"
+
+namespace aapt {
+
+TEST(StringPieceTest, CompareNonNullTerminatedPiece) {
+ 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);
+}
+
+TEST(StringPieceTest, PiecesHaveCorrectSortOrder) {
+ std::u16string testing(u"testing");
+ std::u16string banana(u"banana");
+ std::u16string car(u"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);
+}
+
+TEST(StringPieceTest, PiecesHaveCorrectSortOrderUtf8) {
+ 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);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp
new file mode 100644
index 0000000..c19aa98
--- /dev/null
+++ b/tools/aapt2/StringPool.cpp
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BigBuffer.h"
+#include "StringPiece.h"
+#include "StringPool.h"
+#include "Util.h"
+
+#include <algorithm>
+#include <androidfw/ResourceTypes.h>
+#include <memory>
+#include <string>
+
+namespace aapt {
+
+StringPool::Ref::Ref() : mEntry(nullptr) {
+}
+
+StringPool::Ref::Ref(const StringPool::Ref& rhs) : mEntry(rhs.mEntry) {
+ if (mEntry != nullptr) {
+ mEntry->ref++;
+ }
+}
+
+StringPool::Ref::Ref(StringPool::Entry* entry) : mEntry(entry) {
+ if (mEntry != nullptr) {
+ mEntry->ref++;
+ }
+}
+
+StringPool::Ref::~Ref() {
+ if (mEntry != nullptr) {
+ mEntry->ref--;
+ }
+}
+
+StringPool::Ref& StringPool::Ref::operator=(const StringPool::Ref& rhs) {
+ if (rhs.mEntry != nullptr) {
+ rhs.mEntry->ref++;
+ }
+
+ if (mEntry != nullptr) {
+ mEntry->ref--;
+ }
+ mEntry = rhs.mEntry;
+ return *this;
+}
+
+const std::u16string* StringPool::Ref::operator->() const {
+ return &mEntry->value;
+}
+
+const std::u16string& StringPool::Ref::operator*() const {
+ return mEntry->value;
+}
+
+size_t StringPool::Ref::getIndex() const {
+ return mEntry->index;
+}
+
+const StringPool::Context& StringPool::Ref::getContext() const {
+ return mEntry->context;
+}
+
+StringPool::StyleRef::StyleRef() : mEntry(nullptr) {
+}
+
+StringPool::StyleRef::StyleRef(const StringPool::StyleRef& rhs) : mEntry(rhs.mEntry) {
+ if (mEntry != nullptr) {
+ mEntry->ref++;
+ }
+}
+
+StringPool::StyleRef::StyleRef(StringPool::StyleEntry* entry) : mEntry(entry) {
+ if (mEntry != nullptr) {
+ mEntry->ref++;
+ }
+}
+
+StringPool::StyleRef::~StyleRef() {
+ if (mEntry != nullptr) {
+ mEntry->ref--;
+ }
+}
+
+StringPool::StyleRef& StringPool::StyleRef::operator=(const StringPool::StyleRef& rhs) {
+ if (rhs.mEntry != nullptr) {
+ rhs.mEntry->ref++;
+ }
+
+ if (mEntry != nullptr) {
+ mEntry->ref--;
+ }
+ mEntry = rhs.mEntry;
+ return *this;
+}
+
+const StringPool::StyleEntry* StringPool::StyleRef::operator->() const {
+ return mEntry;
+}
+
+const StringPool::StyleEntry& StringPool::StyleRef::operator*() const {
+ return *mEntry;
+}
+
+size_t StringPool::StyleRef::getIndex() const {
+ return mEntry->str.getIndex();
+}
+
+const StringPool::Context& StringPool::StyleRef::getContext() const {
+ return mEntry->str.getContext();
+}
+
+StringPool::Ref StringPool::makeRef(const StringPiece16& str) {
+ return makeRefImpl(str, Context{}, true);
+}
+
+StringPool::Ref StringPool::makeRef(const StringPiece16& 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);
+ }
+ }
+
+ 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) {
+ 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));
+}
+
+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;
+ }
+
+ // Reorder the styles.
+ std::sort(std::begin(mStyles), std::end(mStyles),
+ [](const std::unique_ptr<StyleEntry>& lhs,
+ const std::unique_ptr<StyleEntry>& rhs) -> bool {
+ return lhs->str.getIndex() < rhs->str.getIndex();
+ }
+ );
+}
+
+template <typename T>
+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;
+}
+
+template <typename T>
+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;
+}
+
+
+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* styleIndices = nullptr;
+ if (!pool.mStyles.empty()) {
+ header->styleCount = pool.mStyles.back()->str.getIndex() + 1;
+ styleIndices = out->nextBlock<uint32_t>(header->styleCount);
+ }
+
+ const size_t beforeStringsIndex = out->size();
+ header->stringsStart = beforeStringsIndex - startIndex;
+
+ for (const auto& entry : pool) {
+ *indices = out->size() - beforeStringsIndex;
+ indices++;
+
+ if (utf8) {
+ std::string encoded = util::utf16ToUtf8(entry->value);
+
+ const size_t totalSize = encodedLengthUnits<char>(entry->value.size())
+ + encodedLengthUnits<char>(encoded.length())
+ + encoded.size() + 1;
+
+ char* data = out->nextBlock<char>(totalSize);
+
+ // First encode the actual UTF16 string length.
+ data = encodeLength(data, entry->value.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;
+
+ char16_t* data = out->nextBlock<char16_t>(totalSize);
+
+ // Encode the actual UTF16 string length.
+ data = encodeLength(data, entry->value.size());
+ strncpy16(data, entry->value.data(), entry->value.size());
+ }
+ }
+
+ 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();
+ }
+ header->header.size = out->size() - startIndex;
+ return 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);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h
new file mode 100644
index 0000000..14304a6
--- /dev/null
+++ b/tools/aapt2/StringPool.h
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_STRING_POOL_H
+#define AAPT_STRING_POOL_H
+
+#include "BigBuffer.h"
+#include "ConfigDescription.h"
+#include "StringPiece.h"
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+struct Span {
+ std::u16string name;
+ uint32_t firstChar;
+ uint32_t lastChar;
+};
+
+struct StyleString {
+ std::u16string str;
+ std::vector<Span> spans;
+};
+
+class StringPool {
+public:
+ struct Context {
+ uint32_t priority;
+ ConfigDescription config;
+ };
+
+ class Entry;
+
+ class Ref {
+ public:
+ Ref();
+ Ref(const Ref&);
+ ~Ref();
+
+ Ref& operator=(const Ref& rhs);
+ const std::u16string* operator->() const;
+ const std::u16string& operator*() const;
+
+ size_t getIndex() const;
+ const Context& getContext() const;
+
+ private:
+ friend class StringPool;
+
+ Ref(Entry* entry);
+
+ Entry* mEntry;
+ };
+
+ class StyleEntry;
+
+ class StyleRef {
+ public:
+ StyleRef();
+ StyleRef(const StyleRef&);
+ ~StyleRef();
+
+ StyleRef& operator=(const StyleRef& rhs);
+ const StyleEntry* operator->() const;
+ const StyleEntry& operator*() const;
+
+ size_t getIndex() const;
+ const Context& getContext() const;
+
+ private:
+ friend class StringPool;
+
+ StyleRef(StyleEntry* entry);
+
+ StyleEntry* mEntry;
+ };
+
+ class Entry {
+ public:
+ std::u16string value;
+ Context context;
+ size_t index;
+
+ private:
+ friend class StringPool;
+ friend class Ref;
+
+ int ref;
+ };
+
+ struct Span {
+ Ref name;
+ uint32_t firstChar;
+ uint32_t lastChar;
+ };
+
+ class StyleEntry {
+ public:
+ Ref str;
+ std::vector<Span> spans;
+
+ 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 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;
+};
+
+//
+// Inline implementation
+//
+
+inline size_t StringPool::size() const {
+ return mStrings.size();
+}
+
+inline StringPool::const_iterator begin(const StringPool& pool) {
+ return pool.mStrings.begin();
+}
+
+inline StringPool::const_iterator end(const StringPool& pool) {
+ return pool.mStrings.end();
+}
+
+} // namespace aapt
+
+#endif // AAPT_STRING_POOL_H
diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp
new file mode 100644
index 0000000..9552937
--- /dev/null
+++ b/tools/aapt2/StringPool_test.cpp
@@ -0,0 +1,223 @@
+/*
+ * 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 "StringPool.h"
+#include "Util.h"
+
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace android;
+
+namespace aapt {
+
+TEST(StringPoolTest, InsertOneString) {
+ StringPool pool;
+
+ StringPool::Ref ref = pool.makeRef(u"wut");
+ EXPECT_EQ(*ref, u"wut");
+}
+
+TEST(StringPoolTest, InsertTwoUniqueStrings) {
+ StringPool pool;
+
+ StringPool::Ref ref = pool.makeRef(u"wut");
+ StringPool::Ref ref2 = pool.makeRef(u"hey");
+
+ EXPECT_EQ(*ref, u"wut");
+ EXPECT_EQ(*ref2, u"hey");
+}
+
+TEST(StringPoolTest, DoNotInsertNewDuplicateString) {
+ StringPool pool;
+
+ StringPool::Ref ref = pool.makeRef(u"wut");
+ StringPool::Ref ref2 = pool.makeRef(u"wut");
+
+ EXPECT_EQ(*ref, u"wut");
+ EXPECT_EQ(*ref2, u"wut");
+ EXPECT_EQ(1u, pool.size());
+}
+
+TEST(StringPoolTest, MaintainInsertionOrderIndex) {
+ StringPool pool;
+
+ StringPool::Ref ref = pool.makeRef(u"z");
+ StringPool::Ref ref2 = pool.makeRef(u"a");
+ StringPool::Ref ref3 = pool.makeRef(u"m");
+
+ EXPECT_EQ(0u, ref.getIndex());
+ EXPECT_EQ(1u, ref2.getIndex());
+ EXPECT_EQ(2u, ref3.getIndex());
+}
+
+TEST(StringPoolTest, PruneStringsWithNoReferences) {
+ StringPool pool;
+
+ {
+ StringPool::Ref ref = pool.makeRef(u"wut");
+ EXPECT_EQ(*ref, u"wut");
+ EXPECT_EQ(1u, pool.size());
+ }
+
+ EXPECT_EQ(1u, pool.size());
+ pool.prune();
+ EXPECT_EQ(0u, pool.size());
+}
+
+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");
+
+ EXPECT_EQ(*ref, u"z");
+ EXPECT_EQ(0u, ref.getIndex());
+
+ EXPECT_EQ(*(ref2->str), u"a");
+ EXPECT_EQ(1u, ref2.getIndex());
+
+ EXPECT_EQ(*ref3, u"m");
+ EXPECT_EQ(2u, ref3.getIndex());
+
+ 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(*(ref2->str), u"a");
+ EXPECT_EQ(0u, ref2.getIndex());
+
+ EXPECT_EQ(*ref3, u"m");
+ EXPECT_EQ(1u, ref3.getIndex());
+}
+
+TEST(StringPoolTest, SortAndStillDedupe) {
+ StringPool pool;
+
+ StringPool::Ref ref = pool.makeRef(u"z");
+ StringPool::Ref ref2 = pool.makeRef(u"a");
+ StringPool::Ref ref3 = pool.makeRef(u"m");
+
+ 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");
+
+ EXPECT_EQ(ref4.getIndex(), ref.getIndex());
+ EXPECT_EQ(ref5.getIndex(), ref2.getIndex());
+ EXPECT_EQ(ref6.getIndex(), ref3.getIndex());
+}
+
+TEST(StringPoolTest, AddStyles) {
+ StringPool pool;
+
+ StyleString str {
+ { u"android" },
+ {
+ Span{ { u"b" }, 2, 6 }
+ }
+ };
+
+ 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());
+
+ const StringPool::Span& span = ref->spans.front();
+ EXPECT_EQ(*(span.name), u"b");
+ EXPECT_EQ(2u, span.firstChar);
+ EXPECT_EQ(6u, span.lastChar);
+}
+
+TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) {
+ StringPool pool;
+
+ StringPool::Ref ref = pool.makeRef(u"android");
+
+ StyleString str { { u"android" } };
+ StringPool::StyleRef styleRef = pool.makeRef(str);
+
+ EXPECT_NE(ref.getIndex(), styleRef.getIndex());
+}
+
+TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) {
+ StringPool pool;
+ BigBuffer buffer(1024);
+ StringPool::flattenUtf8(&buffer, pool);
+
+ std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+ android::ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), android::NO_ERROR);
+}
+
+constexpr const char16_t* sLongString = u"バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限します。メール、SMSや、同期を使 用するその他のアプリは、起動しても更新されないことがあります。バッテリーセーバーは端末の充電中は自動的にOFFになります。";
+
+TEST(StringPoolTest, FlattenUtf8) {
+ 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);
+ {
+ android::ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), android::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);
+ }
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/TableFlattener.cpp b/tools/aapt2/TableFlattener.cpp
new file mode 100644
index 0000000..539c48f
--- /dev/null
+++ b/tools/aapt2/TableFlattener.cpp
@@ -0,0 +1,577 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BigBuffer.h"
+#include "ConfigDescription.h"
+#include "Logger.h"
+#include "ResourceTable.h"
+#include "ResourceTypeExtensions.h"
+#include "ResourceValues.h"
+#include "StringPool.h"
+#include "TableFlattener.h"
+#include "Util.h"
+
+#include <algorithm>
+#include <androidfw/ResourceTypes.h>
+#include <sstream>
+
+namespace aapt {
+
+struct FlatEntry {
+ const ResourceEntry* entry;
+ const Value* value;
+ uint32_t entryKey;
+ uint32_t sourcePathKey;
+ uint32_t sourceLine;
+};
+
+/**
+ * Visitor that knows how to encode Map values.
+ */
+class MapFlattener : public ConstValueVisitor {
+public:
+ MapFlattener(BigBuffer* out, const FlatEntry& flatEntry, SymbolEntryVector* symbols) :
+ mOut(out), mSymbols(symbols) {
+ mMap = mOut->nextBlock<android::ResTable_map_entry>();
+ mMap->key.index = flatEntry.entryKey;
+ mMap->flags = android::ResTable_entry::FLAG_COMPLEX;
+ if (flatEntry.entry->publicStatus.isPublic) {
+ mMap->flags |= android::ResTable_entry::FLAG_PUBLIC;
+ }
+ if (flatEntry.value->isWeak()) {
+ mMap->flags |= android::ResTable_entry::FLAG_WEAK;
+ }
+
+ ResTable_entry_source* sourceBlock = mOut->nextBlock<ResTable_entry_source>();
+ sourceBlock->pathIndex = flatEntry.sourcePathKey;
+ sourceBlock->line = flatEntry.sourceLine;
+
+ mMap->size = sizeof(*mMap) + sizeof(*sourceBlock);
+ }
+
+ void flattenParent(const Reference& ref) {
+ if (!ref.id.isValid()) {
+ mSymbols->push_back({
+ ResourceNameRef(ref.name),
+ (mOut->size() - mMap->size) + sizeof(*mMap) - sizeof(android::ResTable_entry)
+ });
+ }
+ mMap->parent.ident = ref.id.id;
+ }
+
+ void flattenEntry(const Reference& key, const Item& value) {
+ mMap->count++;
+
+ android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>();
+
+ // Write the key.
+ if (!Res_INTERNALID(key.id.id) && !key.id.isValid()) {
+ assert(key.name.isValid());
+ mSymbols->push_back(std::make_pair(ResourceNameRef(key.name),
+ mOut->size() - sizeof(*outMapEntry)));
+ }
+ outMapEntry->name.ident = key.id.id;
+
+ // Write the value.
+ value.flatten(outMapEntry->value);
+
+ if (outMapEntry->value.data == 0x0) {
+ visitFunc<Reference>(value, [&](const Reference& reference) {
+ mSymbols->push_back(std::make_pair(ResourceNameRef(reference.name),
+ mOut->size() - sizeof(outMapEntry->value.data)));
+ });
+ }
+ outMapEntry->value.size = sizeof(outMapEntry->value);
+ }
+
+ void flattenValueOnly(const Item& value) {
+ mMap->count++;
+
+ android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>();
+
+ // Write the value.
+ value.flatten(outMapEntry->value);
+
+ if (outMapEntry->value.data == 0x0) {
+ visitFunc<Reference>(value, [&](const Reference& reference) {
+ mSymbols->push_back(std::make_pair(ResourceNameRef(reference.name),
+ mOut->size() - sizeof(outMapEntry->value.data)));
+ });
+ }
+ outMapEntry->value.size = sizeof(outMapEntry->value);
+ }
+
+ static bool compareStyleEntries(const Style::Entry* lhs, const Style::Entry* rhs) {
+ return lhs->key.id < rhs->key.id;
+ }
+
+ void visit(const Style& style, ValueVisitorArgs&) override {
+ if (style.parent.name.isValid()) {
+ flattenParent(style.parent);
+ }
+
+ // First sort the entries by ID.
+ std::vector<const Style::Entry*> sortedEntries;
+ for (const auto& styleEntry : style.entries) {
+ auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(),
+ &styleEntry, compareStyleEntries);
+ sortedEntries.insert(iter, &styleEntry);
+ }
+
+ for (const Style::Entry* styleEntry : sortedEntries) {
+ flattenEntry(styleEntry->key, *styleEntry->value);
+ }
+ }
+
+ void visit(const Attribute& attr, ValueVisitorArgs&) override {
+ android::Res_value tempVal;
+ tempVal.dataType = android::Res_value::TYPE_INT_DEC;
+ tempVal.data = attr.typeMask;
+ flattenEntry(Reference(ResourceId{android::ResTable_map::ATTR_TYPE}),
+ BinaryPrimitive(tempVal));
+
+ for (const auto& symbol : attr.symbols) {
+ tempVal.data = symbol.value;
+ flattenEntry(symbol.symbol, BinaryPrimitive(tempVal));
+ }
+ }
+
+ void visit(const Styleable& styleable, ValueVisitorArgs&) override {
+ for (const auto& attr : styleable.entries) {
+ flattenEntry(attr, BinaryPrimitive(android::Res_value{}));
+ }
+ }
+
+ void visit(const Array& array, ValueVisitorArgs&) override {
+ for (const auto& item : array.items) {
+ flattenValueOnly(*item);
+ }
+ }
+
+ void visit(const Plural& plural, ValueVisitorArgs&) override {
+ const size_t count = plural.values.size();
+ for (size_t i = 0; i < count; i++) {
+ if (!plural.values[i]) {
+ continue;
+ }
+
+ ResourceId q;
+ switch (i) {
+ case Plural::Zero:
+ q.id = android::ResTable_map::ATTR_ZERO;
+ break;
+
+ case Plural::One:
+ q.id = android::ResTable_map::ATTR_ONE;
+ break;
+
+ case Plural::Two:
+ q.id = android::ResTable_map::ATTR_TWO;
+ break;
+
+ case Plural::Few:
+ q.id = android::ResTable_map::ATTR_FEW;
+ break;
+
+ case Plural::Many:
+ q.id = android::ResTable_map::ATTR_MANY;
+ break;
+
+ case Plural::Other:
+ q.id = android::ResTable_map::ATTR_OTHER;
+ break;
+
+ default:
+ assert(false);
+ break;
+ }
+
+ flattenEntry(Reference(q), *plural.values[i]);
+ }
+ }
+
+private:
+ BigBuffer* mOut;
+ SymbolEntryVector* mSymbols;
+ android::ResTable_map_entry* mMap;
+};
+
+/**
+ * Flattens a value, with special handling for References.
+ */
+struct ValueFlattener : ConstValueVisitor {
+ ValueFlattener(BigBuffer* out, SymbolEntryVector* symbols) :
+ result(false), mOut(out), mOutValue(nullptr), mSymbols(symbols) {
+ mOutValue = mOut->nextBlock<android::Res_value>();
+ }
+
+ virtual void visit(const Reference& ref, ValueVisitorArgs& a) override {
+ visitItem(ref, a);
+ if (mOutValue->data == 0x0) {
+ mSymbols->push_back({
+ ResourceNameRef(ref.name),
+ mOut->size() - sizeof(mOutValue->data)});
+ }
+ }
+
+ virtual void visitItem(const Item& item, ValueVisitorArgs&) override {
+ result = item.flatten(*mOutValue);
+ mOutValue->res0 = 0;
+ mOutValue->size = sizeof(*mOutValue);
+ }
+
+ bool result;
+
+private:
+ BigBuffer* mOut;
+ android::Res_value* mOutValue;
+ SymbolEntryVector* mSymbols;
+};
+
+TableFlattener::TableFlattener(Options options)
+: mOptions(options) {
+}
+
+bool TableFlattener::flattenValue(BigBuffer* out, const FlatEntry& flatEntry,
+ SymbolEntryVector* symbols) {
+ if (flatEntry.value->isItem()) {
+ android::ResTable_entry* entry = out->nextBlock<android::ResTable_entry>();
+
+ if (flatEntry.entry->publicStatus.isPublic) {
+ entry->flags |= android::ResTable_entry::FLAG_PUBLIC;
+ }
+
+ if (flatEntry.value->isWeak()) {
+ entry->flags |= android::ResTable_entry::FLAG_WEAK;
+ }
+
+ entry->key.index = flatEntry.entryKey;
+ entry->size = sizeof(*entry);
+
+ if (mOptions.useExtendedChunks) {
+ // Write the extra source block. This will be ignored by
+ // the Android runtime.
+ ResTable_entry_source* sourceBlock = out->nextBlock<ResTable_entry_source>();
+ sourceBlock->pathIndex = flatEntry.sourcePathKey;
+ sourceBlock->line = flatEntry.sourceLine;
+ entry->size += sizeof(*sourceBlock);
+ }
+
+ const Item* item = static_cast<const Item*>(flatEntry.value);
+ ValueFlattener flattener(out, symbols);
+ item->accept(flattener, {});
+ return flattener.result;
+ }
+
+ MapFlattener flattener(out, flatEntry, symbols);
+ flatEntry.value->accept(flattener, {});
+ return true;
+}
+
+bool TableFlattener::flatten(BigBuffer* out, const ResourceTable& table) {
+ const size_t beginning = out->size();
+
+ if (table.getPackage().size() == 0) {
+ Logger::error()
+ << "ResourceTable has no package name."
+ << std::endl;
+ return false;
+ }
+
+ if (table.getPackageId() == ResourceTable::kUnsetPackageId) {
+ Logger::error()
+ << "ResourceTable has no package ID set."
+ << std::endl;
+ return false;
+ }
+
+ SymbolEntryVector symbolEntries;
+
+ StringPool typePool;
+ StringPool keyPool;
+ StringPool sourcePool;
+
+ // Sort the types by their IDs. They will be inserted into the StringPool
+ // in this order.
+ std::vector<ResourceTableType*> sortedTypes;
+ for (const auto& type : table) {
+ if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) {
+ continue;
+ }
+
+ auto iter = std::lower_bound(std::begin(sortedTypes), std::end(sortedTypes), type.get(),
+ [](const ResourceTableType* lhs, const ResourceTableType* rhs) -> bool {
+ return lhs->typeId < rhs->typeId;
+ });
+ sortedTypes.insert(iter, type.get());
+ }
+
+ BigBuffer typeBlock(1024);
+ size_t expectedTypeId = 1;
+ for (const ResourceTableType* type : sortedTypes) {
+ if (type->typeId == ResourceTableType::kUnsetTypeId
+ || type->typeId == 0) {
+ Logger::error()
+ << "resource type '"
+ << type->type
+ << "' from package '"
+ << table.getPackage()
+ << "' has no ID."
+ << std::endl;
+ return false;
+ }
+
+ // If there is a gap in the type IDs, fill in the StringPool
+ // with empty values until we reach the ID we expect.
+ while (type->typeId > expectedTypeId) {
+ std::u16string typeName(u"?");
+ typeName += expectedTypeId;
+ typePool.makeRef(typeName);
+ expectedTypeId++;
+ }
+ expectedTypeId++;
+ typePool.makeRef(toString(type->type));
+
+ android::ResTable_typeSpec* spec = typeBlock.nextBlock<android::ResTable_typeSpec>();
+ spec->header.type = android::RES_TABLE_TYPE_SPEC_TYPE;
+ spec->header.headerSize = sizeof(*spec);
+ spec->header.size = spec->header.headerSize + (type->entries.size() * sizeof(uint32_t));
+ spec->id = type->typeId;
+ spec->entryCount = type->entries.size();
+
+ if (type->entries.empty()) {
+ continue;
+ }
+
+ // Reserve space for the masks of each resource in this type. These
+ // show for which configuration axis the resource changes.
+ uint32_t* configMasks = typeBlock.nextBlock<uint32_t>(type->entries.size());
+
+ // Sort the entries by entry ID and write their configuration masks.
+ std::vector<ResourceEntry*> entries;
+ const size_t entryCount = type->entries.size();
+ for (size_t entryIndex = 0; entryIndex < entryCount; entryIndex++) {
+ const auto& entry = type->entries[entryIndex];
+
+ if (entry->entryId == ResourceEntry::kUnsetEntryId) {
+ Logger::error()
+ << "resource '"
+ << ResourceName{ table.getPackage(), type->type, entry->name }
+ << "' has no ID."
+ << std::endl;
+ return false;
+ }
+
+ auto iter = std::lower_bound(std::begin(entries), std::end(entries), entry.get(),
+ [](const ResourceEntry* lhs, const ResourceEntry* rhs) -> bool {
+ return lhs->entryId < rhs->entryId;
+ });
+ entries.insert(iter, entry.get());
+
+ // Populate the config masks for this entry.
+ if (entry->publicStatus.isPublic) {
+ configMasks[entry->entryId] |= android::ResTable_typeSpec::SPEC_PUBLIC;
+ }
+
+ const size_t configCount = entry->values.size();
+ for (size_t i = 0; i < configCount; i++) {
+ const ConfigDescription& config = entry->values[i].config;
+ for (size_t j = i + 1; j < configCount; j++) {
+ configMasks[entry->entryId] |= config.diff(entry->values[j].config);
+ }
+ }
+ }
+
+ const size_t beforePublicHeader = typeBlock.size();
+ Public_header* publicHeader = nullptr;
+ if (mOptions.useExtendedChunks) {
+ publicHeader = typeBlock.nextBlock<Public_header>();
+ publicHeader->header.type = RES_TABLE_PUBLIC_TYPE;
+ publicHeader->header.headerSize = sizeof(*publicHeader);
+ publicHeader->typeId = type->typeId;
+ }
+
+ // The binary resource table lists resource entries for each configuration.
+ // We store them inverted, where a resource entry lists the values for each
+ // configuration available. Here we reverse this to match the binary table.
+ std::map<ConfigDescription, std::vector<FlatEntry>> data;
+ for (const ResourceEntry* entry : entries) {
+ size_t keyIndex = keyPool.makeRef(entry->name).getIndex();
+
+ if (keyIndex > std::numeric_limits<uint32_t>::max()) {
+ Logger::error()
+ << "resource key string pool exceeded max size."
+ << std::endl;
+ return false;
+ }
+
+ if (publicHeader && entry->publicStatus.isPublic) {
+ // Write the public status of this entry.
+ Public_entry* publicEntry = typeBlock.nextBlock<Public_entry>();
+ publicEntry->entryId = static_cast<uint32_t>(entry->entryId);
+ publicEntry->key.index = static_cast<uint32_t>(keyIndex);
+ publicEntry->source.index = static_cast<uint32_t>(sourcePool.makeRef(
+ util::utf8ToUtf16(entry->publicStatus.source.path)).getIndex());
+ publicEntry->sourceLine = static_cast<uint32_t>(entry->publicStatus.source.line);
+ publicHeader->count += 1;
+ }
+
+ for (const auto& configValue : entry->values) {
+ data[configValue.config].push_back(FlatEntry{
+ entry,
+ configValue.value.get(),
+ static_cast<uint32_t>(keyIndex),
+ static_cast<uint32_t>(sourcePool.makeRef(util::utf8ToUtf16(
+ configValue.source.path)).getIndex()),
+ static_cast<uint32_t>(configValue.source.line)
+ });
+ }
+ }
+
+ if (publicHeader) {
+ typeBlock.align4();
+ publicHeader->header.size =
+ static_cast<uint32_t>(typeBlock.size() - beforePublicHeader);
+ }
+
+ // Begin flattening a configuration for the current type.
+ for (const auto& entry : data) {
+ const size_t typeHeaderStart = typeBlock.size();
+ android::ResTable_type* typeHeader = typeBlock.nextBlock<android::ResTable_type>();
+ typeHeader->header.type = android::RES_TABLE_TYPE_TYPE;
+ typeHeader->header.headerSize = sizeof(*typeHeader);
+ typeHeader->id = type->typeId;
+ typeHeader->entryCount = type->entries.size();
+ typeHeader->entriesStart = typeHeader->header.headerSize
+ + (sizeof(uint32_t) * type->entries.size());
+ typeHeader->config = entry.first;
+
+ uint32_t* indices = typeBlock.nextBlock<uint32_t>(type->entries.size());
+ memset(indices, 0xff, type->entries.size() * sizeof(uint32_t));
+
+ const size_t entryStart = typeBlock.size();
+ for (const FlatEntry& flatEntry : entry.second) {
+ assert(flatEntry.entry->entryId < type->entries.size());
+ indices[flatEntry.entry->entryId] = typeBlock.size() - entryStart;
+ if (!flattenValue(&typeBlock, flatEntry, &symbolEntries)) {
+ Logger::error()
+ << "failed to flatten resource '"
+ << ResourceNameRef {
+ table.getPackage(), type->type, flatEntry.entry->name }
+ << "' for configuration '"
+ << entry.first
+ << "'."
+ << std::endl;
+ return false;
+ }
+ }
+
+ typeBlock.align4();
+ typeHeader->header.size = typeBlock.size() - typeHeaderStart;
+ }
+ }
+
+ const size_t beforeTable = out->size();
+ android::ResTable_header* header = out->nextBlock<android::ResTable_header>();
+ header->header.type = android::RES_TABLE_TYPE;
+ header->header.headerSize = sizeof(*header);
+ header->packageCount = 1;
+
+ SymbolTable_entry* symbolEntryData = nullptr;
+ if (!symbolEntries.empty() && mOptions.useExtendedChunks) {
+ const size_t beforeSymbolTable = out->size();
+ StringPool symbolPool;
+ SymbolTable_header* symbolHeader = out->nextBlock<SymbolTable_header>();
+ symbolHeader->header.type = RES_TABLE_SYMBOL_TABLE_TYPE;
+ symbolHeader->header.headerSize = sizeof(*symbolHeader);
+ symbolHeader->count = symbolEntries.size();
+
+ symbolEntryData = out->nextBlock<SymbolTable_entry>(symbolHeader->count);
+
+ size_t i = 0;
+ for (const auto& entry : symbolEntries) {
+ symbolEntryData[i].offset = entry.second;
+ StringPool::Ref ref = symbolPool.makeRef(
+ entry.first.package.toString() + u":" +
+ toString(entry.first.type).toString() + u"/" +
+ entry.first.entry.toString());
+ symbolEntryData[i].stringIndex = ref.getIndex();
+ i++;
+ }
+
+ StringPool::flattenUtf8(out, symbolPool);
+ out->align4();
+ symbolHeader->header.size = out->size() - beforeSymbolTable;
+ }
+
+ if (sourcePool.size() > 0 && mOptions.useExtendedChunks) {
+ const size_t beforeSourcePool = out->size();
+ android::ResChunk_header* sourceHeader = out->nextBlock<android::ResChunk_header>();
+ sourceHeader->type = RES_TABLE_SOURCE_POOL_TYPE;
+ sourceHeader->headerSize = sizeof(*sourceHeader);
+ StringPool::flattenUtf8(out, sourcePool);
+ out->align4();
+ sourceHeader->size = out->size() - beforeSourcePool;
+ }
+
+ StringPool::flattenUtf8(out, table.getValueStringPool());
+
+ const size_t beforePackageIndex = out->size();
+ android::ResTable_package* package = out->nextBlock<android::ResTable_package>();
+ package->header.type = android::RES_TABLE_PACKAGE_TYPE;
+ package->header.headerSize = sizeof(*package);
+
+ if (table.getPackageId() > std::numeric_limits<uint8_t>::max()) {
+ Logger::error()
+ << "package ID 0x'"
+ << std::hex << table.getPackageId() << std::dec
+ << "' is invalid."
+ << std::endl;
+ return false;
+ }
+ package->id = table.getPackageId();
+
+ if (table.getPackage().size() >= sizeof(package->name) / sizeof(package->name[0])) {
+ Logger::error()
+ << "package name '"
+ << table.getPackage()
+ << "' is too long."
+ << std::endl;
+ return false;
+ }
+ memcpy(package->name, reinterpret_cast<const uint16_t*>(table.getPackage().data()),
+ table.getPackage().length() * sizeof(char16_t));
+ package->name[table.getPackage().length()] = 0;
+
+ package->typeStrings = package->header.headerSize;
+ StringPool::flattenUtf16(out, typePool);
+ package->keyStrings = out->size() - beforePackageIndex;
+ StringPool::flattenUtf16(out, keyPool);
+
+ if (symbolEntryData != nullptr) {
+ for (size_t i = 0; i < symbolEntries.size(); i++) {
+ symbolEntryData[i].offset += out->size() - beginning;
+ }
+ }
+
+ out->appendBuffer(std::move(typeBlock));
+
+ package->header.size = out->size() - beforePackageIndex;
+ header->header.size = out->size() - beforeTable;
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/TableFlattener.h b/tools/aapt2/TableFlattener.h
new file mode 100644
index 0000000..ccbb737
--- /dev/null
+++ b/tools/aapt2/TableFlattener.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_TABLE_FLATTENER_H
+#define AAPT_TABLE_FLATTENER_H
+
+#include "BigBuffer.h"
+#include "ResourceTable.h"
+
+namespace aapt {
+
+using SymbolEntryVector = std::vector<std::pair<ResourceNameRef, uint32_t>>;
+
+struct FlatEntry;
+
+/**
+ * Flattens a ResourceTable into a binary format suitable
+ * for loading into a ResTable on the host or device.
+ */
+struct TableFlattener {
+ /**
+ * A set of options for this TableFlattener.
+ */
+ struct Options {
+ /**
+ * Specifies whether to output extended chunks, like
+ * source information and mising symbol entries. Default
+ * is true.
+ *
+ * Set this to false when emitting the final table to be used
+ * on device.
+ */
+ bool useExtendedChunks = true;
+ };
+
+ TableFlattener(Options options);
+
+ bool flatten(BigBuffer* out, const ResourceTable& table);
+
+private:
+ bool flattenValue(BigBuffer* out, const FlatEntry& flatEntry, SymbolEntryVector* symbols);
+
+ Options mOptions;
+};
+
+} // namespace aapt
+
+#endif // AAPT_TABLE_FLATTENER_H
diff --git a/tools/aapt2/Util.cpp b/tools/aapt2/Util.cpp
new file mode 100644
index 0000000..03ecd1a
--- /dev/null
+++ b/tools/aapt2/Util.cpp
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BigBuffer.h"
+#include "Maybe.h"
+#include "StringPiece.h"
+#include "Util.h"
+
+#include <algorithm>
+#include <ostream>
+#include <string>
+#include <utils/Unicode.h>
+#include <vector>
+
+namespace aapt {
+namespace util {
+
+constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto";
+constexpr const char16_t* kSchemaPrefix = u"http://schemas.android.com/apk/res/";
+
+static std::vector<std::string> splitAndTransform(const StringPiece& str, char sep,
+ const std::function<char(char)>& f) {
+ std::vector<std::string> parts;
+ 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> 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--;
+ }
+
+ return StringPiece16(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;
+ }
+ }
+ 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;
+ }
+
+ if (findNonAlphaNumericAndNotInSet(piece, u"$_") != piece.end()) {
+ return false;
+ }
+ }
+ return pieces >= 2;
+}
+
+Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package,
+ const StringPiece16& className) {
+ if (className.empty()) {
+ return {};
+ }
+
+ if (util::isJavaClassName(className)) {
+ return className.toString();
+ }
+
+ if (package.empty()) {
+ return {};
+ }
+
+ std::u16string result(package.data(), package.size());
+ if (className.data()[0] != u'.') {
+ result += u'.';
+ }
+ result.append(className.data(), className.size());
+ if (!isJavaClassName(result)) {
+ return {};
+ }
+ return result;
+}
+
+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;
+ }
+ return make_value(code);
+}
+
+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 (*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";
+ 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;
+
+ current++;
+ if (current != end) {
+ switch (*current) {
+ case u't':
+ mStr += u'\t';
+ break;
+ case u'n':
+ mStr += u'\n';
+ break;
+ case u'#':
+ mStr += u'#';
+ break;
+ case u'@':
+ mStr += u'@';
+ break;
+ case u'?':
+ mStr += u'?';
+ break;
+ case u'"':
+ mStr += u'"';
+ break;
+ case u'\'':
+ mStr += u'\'';
+ break;
+ case u'\\':
+ mStr += u'\\';
+ break;
+ case u'u': {
+ current++;
+ Maybe<char16_t> c = parseUnicodeCodepoint(&current, end);
+ if (!c) {
+ mError = "invalid unicode escape sequence";
+ return *this;
+ }
+ mStr += c.value();
+ current -= 1;
+ break;
+ }
+
+ default:
+ // Ignore.
+ break;
+ }
+ start = current + 1;
+ }
+ } 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;
+ }
+ }
+ current++;
+ }
+ mStr.append(start, end - start);
+ 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 utf16;
+ utf16.resize(utf16Length);
+ utf8_to_utf16(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length(), &*utf16.begin());
+ return utf16;
+}
+
+std::string utf16ToUtf8(const StringPiece16& utf16) {
+ ssize_t utf8Length = utf16_to_utf8_length(utf16.data(), utf16.length());
+ if (utf8Length <= 0) {
+ return {};
+ }
+
+ std::string utf8;
+ utf8.resize(utf8Length);
+ utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin());
+ return utf8;
+}
+
+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::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;
+}
+
+Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri) {
+ if (stringStartsWith<char16_t>(namespaceUri, kSchemaPrefix)) {
+ StringPiece16 schemaPrefix = kSchemaPrefix;
+ StringPiece16 package = namespaceUri;
+ return package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size())
+ .toString();
+ } else if (namespaceUri == kSchemaAuto) {
+ return std::u16string();
+ }
+ return {};
+}
+
+} // namespace util
+} // namespace aapt
diff --git a/tools/aapt2/Util.h b/tools/aapt2/Util.h
new file mode 100644
index 0000000..9cdb152
--- /dev/null
+++ b/tools/aapt2/Util.h
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_UTIL_H
+#define AAPT_UTIL_H
+
+#include "BigBuffer.h"
+#include "Maybe.h"
+#include "StringPiece.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <functional>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <vector>
+
+namespace aapt {
+namespace util {
+
+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;
+}
+
+/**
+ * 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;
+}
+
+/**
+ * Creates a new StringPiece16 that points to a substring
+ * of the original string without leading or trailing whitespace.
+ */
+StringPiece16 trimWhitespace(const StringPiece16& 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);
+}
+
+/**
+ * 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);
+
+/**
+ * Tests that the string is a valid Java class name.
+ */
+bool isJavaClassName(const StringPiece16& str);
+
+/**
+ * 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);
+
+
+/**
+ * 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)...});
+}
+
+/**
+ * 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;
+ };
+}
+
+/**
+ * Helper method to extract a string from a StringPool.
+ */
+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();
+}
+
+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;
+ std::string mError;
+};
+
+inline const std::u16string& StringBuilder::str() const {
+ return mStr;
+}
+
+inline const std::string& StringBuilder::error() const {
+ return mError;
+}
+
+inline StringBuilder::operator bool() const {
+ return mError.empty();
+}
+
+/**
+ * Converts a UTF8 string to a UTF16 string.
+ */
+std::u16string utf8ToUtf16(const StringPiece& utf8);
+std::string utf16ToUtf8(const StringPiece16& utf8);
+
+/**
+ * Writes the entire BigBuffer to the output stream.
+ */
+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);
+
+/**
+ * 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);
+
+ BasicStringPiece<Char> str;
+ Char separator;
+ BasicStringPiece<Char> token;
+ };
+
+ Tokenizer(BasicStringPiece<Char> str, Char sep);
+ iterator begin();
+ iterator end();
+
+private:
+ const iterator mBegin;
+ const iterator mEnd;
+};
+
+template <typename Char>
+inline Tokenizer<Char> tokenize(BasicStringPiece<Char> str, Char sep) {
+ return Tokenizer<Char>(str, sep);
+}
+
+template <typename Char>
+typename Tokenizer<Char>::iterator& Tokenizer<Char>::iterator::operator++() {
+ const Char* start = token.end();
+ const Char* end = str.end();
+ if (start == end) {
+ 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;
+ }
+ ++current;
+ }
+ token.assign(start, end - start);
+ return *this;
+}
+
+template <typename Char>
+inline BasicStringPiece<Char> Tokenizer<Char>::iterator::operator*() {
+ return token;
+}
+
+template <typename Char>
+inline bool Tokenizer<Char>::iterator::operator==(const iterator& rhs) const {
+ // We check equality here a bit differently.
+ // We need to know that the addresses are the same.
+ return token.begin() == rhs.token.begin() && token.end() == rhs.token.end();
+}
+
+template <typename Char>
+inline bool Tokenizer<Char>::iterator::operator!=(const iterator& rhs) const {
+ return !(*this == rhs);
+}
+
+template <typename Char>
+inline Tokenizer<Char>::iterator::iterator(BasicStringPiece<Char> s, Char sep,
+ BasicStringPiece<Char> tok) :
+ str(s), separator(sep), token(tok) {
+}
+
+template <typename Char>
+inline typename Tokenizer<Char>::iterator Tokenizer<Char>::begin() {
+ return mBegin;
+}
+
+template <typename Char>
+inline typename Tokenizer<Char>::iterator Tokenizer<Char>::end() {
+ return mEnd;
+}
+
+template <typename Char>
+inline Tokenizer<Char>::Tokenizer(BasicStringPiece<Char> str, Char sep) :
+ mBegin(++iterator(str, sep, BasicStringPiece<Char>(str.begin() - 1, 0))),
+ mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0)) {
+}
+
+/**
+ * Returns a package name if the namespace URI is of the form:
+ * http://schemas.android.com/apk/res/<package>
+ *
+ * Special case: if namespaceUri is http://schemas.android.com/apk/res-auto,
+ * returns an empty package name.
+ */
+Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri);
+
+} // namespace util
+
+/**
+ * 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);
+}
+
+} // namespace aapt
+
+#endif // AAPT_UTIL_H
diff --git a/tools/aapt2/Util_test.cpp b/tools/aapt2/Util_test.cpp
new file mode 100644
index 0000000..0b08d24
--- /dev/null
+++ b/tools/aapt2/Util_test.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <string>
+
+#include "StringPiece.h"
+#include "Util.h"
+
+namespace aapt {
+
+TEST(UtilTest, TrimOnlyWhitespace) {
+ const std::u16string full = u"\n ";
+
+ StringPiece16 trimmed = util::trimWhitespace(full);
+ EXPECT_TRUE(trimmed.empty());
+ EXPECT_EQ(0u, trimmed.size());
+}
+
+TEST(UtilTest, StringEndsWith) {
+ EXPECT_TRUE(util::stringEndsWith<char>("hello.xml", ".xml"));
+}
+
+TEST(UtilTest, StringStartsWith) {
+ EXPECT_TRUE(util::stringStartsWith<char>("hello.xml", "he"));
+}
+
+TEST(UtilTest, StringBuilderWhitespaceRemoval) {
+ EXPECT_EQ(StringPiece16(u"hey guys this is so cool"),
+ util::StringBuilder().append(u" hey guys ")
+ .append(u" this is so cool ")
+ .str());
+
+ EXPECT_EQ(StringPiece16(u" wow, so many \t spaces. what?"),
+ util::StringBuilder().append(u" \" wow, so many \t ")
+ .append(u"spaces. \"what? ")
+ .str());
+
+ EXPECT_EQ(StringPiece16(u"where is the pie?"),
+ util::StringBuilder().append(u" where \t ")
+ .append(u" \nis the "" pie?")
+ .str());
+}
+
+TEST(UtilTest, StringBuilderEscaping) {
+ EXPECT_EQ(StringPiece16(u"hey guys\n this \t is so\\ cool"),
+ util::StringBuilder().append(u" hey guys\\n ")
+ .append(u" this \\t is so\\\\ cool ")
+ .str());
+
+ EXPECT_EQ(StringPiece16(u"@?#\\\'"),
+ util::StringBuilder().append(u"\\@\\?\\#\\\\\\'")
+ .str());
+}
+
+TEST(UtilTest, StringBuilderMisplacedQuote) {
+ util::StringBuilder builder{};
+ EXPECT_FALSE(builder.append(u"they're coming!"));
+}
+
+TEST(UtilTest, StringBuilderUnicodeCodes) {
+ EXPECT_EQ(StringPiece16(u"\u00AF\u0AF0 woah"),
+ util::StringBuilder().append(u"\\u00AF\\u0AF0 woah")
+ .str());
+
+ EXPECT_FALSE(util::StringBuilder().append(u"\\u00 yo"));
+}
+
+TEST(UtilTest, TokenizeInput) {
+ auto tokenizer = util::tokenize(StringPiece16(u"this| is|the|end"), u'|');
+ auto iter = tokenizer.begin();
+ ASSERT_EQ(*iter, StringPiece16(u"this"));
+ ++iter;
+ ASSERT_EQ(*iter, StringPiece16(u" is"));
+ ++iter;
+ ASSERT_EQ(*iter, StringPiece16(u"the"));
+ ++iter;
+ ASSERT_EQ(*iter, StringPiece16(u"end"));
+ ++iter;
+ ASSERT_EQ(tokenizer.end(), iter);
+}
+
+TEST(UtilTest, IsJavaClassName) {
+ EXPECT_TRUE(util::isJavaClassName(u"android.test.Class"));
+ EXPECT_TRUE(util::isJavaClassName(u"android.test.Class$Inner"));
+ EXPECT_TRUE(util::isJavaClassName(u"android_test.test.Class"));
+ EXPECT_TRUE(util::isJavaClassName(u"_android_.test._Class_"));
+ EXPECT_FALSE(util::isJavaClassName(u"android.test.$Inner"));
+ EXPECT_FALSE(util::isJavaClassName(u"android.test.Inner$"));
+ EXPECT_FALSE(util::isJavaClassName(u".test.Class"));
+ EXPECT_FALSE(util::isJavaClassName(u"android"));
+}
+
+TEST(UtilTest, FullyQualifiedClassName) {
+ Maybe<std::u16string> res = util::getFullyQualifiedClassName(u"android", u"asdf");
+ ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), u"android.asdf");
+
+ res = util::getFullyQualifiedClassName(u"android", u".asdf");
+ ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), u"android.asdf");
+
+ res = util::getFullyQualifiedClassName(u"android", u".a.b");
+ ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), u"android.a.b");
+
+ res = util::getFullyQualifiedClassName(u"android", u"a.b");
+ ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), u"a.b");
+
+ res = util::getFullyQualifiedClassName(u"", u"a.b");
+ ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), u"a.b");
+
+ res = util::getFullyQualifiedClassName(u"", u"");
+ ASSERT_FALSE(res);
+
+ res = util::getFullyQualifiedClassName(u"android", u"./Apple");
+ ASSERT_FALSE(res);
+}
+
+
+} // namespace aapt
diff --git a/tools/aapt2/XliffXmlPullParser.cpp b/tools/aapt2/XliffXmlPullParser.cpp
new file mode 100644
index 0000000..31115f2
--- /dev/null
+++ b/tools/aapt2/XliffXmlPullParser.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "XliffXmlPullParser.h"
+
+#include <string>
+
+namespace aapt {
+
+XliffXmlPullParser::XliffXmlPullParser(const std::shared_ptr<XmlPullParser>& parser) :
+ mParser(parser) {
+}
+
+XmlPullParser::Event XliffXmlPullParser::next() {
+ while (XmlPullParser::isGoodEvent(mParser->next())) {
+ Event event = mParser->getEvent();
+ if (event != Event::kStartElement && event != Event::kEndElement) {
+ break;
+ }
+
+ if (mParser->getElementNamespace() !=
+ u"urn:oasis:names:tc:xliff:document:1.2") {
+ break;
+ }
+
+ const std::u16string& name = mParser->getElementName();
+ if (name != u"bpt"
+ && name != u"ept"
+ && name != u"it"
+ && name != u"ph"
+ && name != u"g"
+ && name != u"bx"
+ && name != u"ex"
+ && name != u"x") {
+ break;
+ }
+
+ // We hit a tag that was ignored, so get the next event.
+ }
+ return mParser->getEvent();
+}
+
+XmlPullParser::Event XliffXmlPullParser::getEvent() const {
+ return mParser->getEvent();
+}
+
+const std::string& XliffXmlPullParser::getLastError() const {
+ return mParser->getLastError();
+}
+
+const std::u16string& XliffXmlPullParser::getComment() const {
+ return mParser->getComment();
+}
+
+size_t XliffXmlPullParser::getLineNumber() const {
+ return mParser->getLineNumber();
+}
+
+size_t XliffXmlPullParser::getDepth() const {
+ return mParser->getDepth();
+}
+
+const std::u16string& XliffXmlPullParser::getText() const {
+ return mParser->getText();
+}
+
+const std::u16string& XliffXmlPullParser::getNamespacePrefix() const {
+ return mParser->getNamespacePrefix();
+}
+
+const std::u16string& XliffXmlPullParser::getNamespaceUri() const {
+ return mParser->getNamespaceUri();
+}
+
+bool XliffXmlPullParser::applyPackageAlias(std::u16string* package,
+ const std::u16string& defaultPackage) const {
+ return mParser->applyPackageAlias(package, defaultPackage);
+}
+
+const std::u16string& XliffXmlPullParser::getElementNamespace() const {
+ return mParser->getElementNamespace();
+}
+
+const std::u16string& XliffXmlPullParser::getElementName() const {
+ return mParser->getElementName();
+}
+
+size_t XliffXmlPullParser::getAttributeCount() const {
+ return mParser->getAttributeCount();
+}
+
+XmlPullParser::const_iterator XliffXmlPullParser::beginAttributes() const {
+ return mParser->beginAttributes();
+}
+
+XmlPullParser::const_iterator XliffXmlPullParser::endAttributes() const {
+ return mParser->endAttributes();
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/XliffXmlPullParser.h b/tools/aapt2/XliffXmlPullParser.h
new file mode 100644
index 0000000..7791227
--- /dev/null
+++ b/tools/aapt2/XliffXmlPullParser.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_XLIFF_XML_PULL_PARSER_H
+#define AAPT_XLIFF_XML_PULL_PARSER_H
+
+#include "XmlPullParser.h"
+
+#include <memory>
+#include <string>
+
+namespace aapt {
+
+/**
+ * Strips xliff elements and provides the caller with a view of the
+ * underlying XML without xliff.
+ */
+class XliffXmlPullParser : public XmlPullParser {
+public:
+ XliffXmlPullParser(const std::shared_ptr<XmlPullParser>& parser);
+ XliffXmlPullParser(const XliffXmlPullParser& rhs) = delete;
+
+ Event getEvent() const override;
+ const std::string& getLastError() const override;
+ Event next() override;
+
+ const std::u16string& getComment() const override;
+ size_t getLineNumber() const override;
+ size_t getDepth() const override;
+
+ const std::u16string& getText() const override;
+
+ const std::u16string& getNamespacePrefix() const override;
+ const std::u16string& getNamespaceUri() const override;
+ bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage)
+ const override;
+
+ const std::u16string& getElementNamespace() const override;
+ const std::u16string& getElementName() const override;
+
+ const_iterator beginAttributes() const override;
+ const_iterator endAttributes() const override;
+ size_t getAttributeCount() const override;
+
+private:
+ std::shared_ptr<XmlPullParser> mParser;
+};
+
+} // namespace aapt
+
+#endif // AAPT_XLIFF_XML_PULL_PARSER_H
diff --git a/tools/aapt2/XliffXmlPullParser_test.cpp b/tools/aapt2/XliffXmlPullParser_test.cpp
new file mode 100644
index 0000000..f903072
--- /dev/null
+++ b/tools/aapt2/XliffXmlPullParser_test.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SourceXmlPullParser.h"
+#include "XliffXmlPullParser.h"
+
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+TEST(XliffXmlPullParserTest, IgnoreXliffTags) {
+ std::stringstream input;
+ input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
+ << "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">" << std::endl
+ << "<string name=\"foo\">"
+ << "Hey <xliff:g><xliff:it>there</xliff:it></xliff:g> world</string>" << std::endl
+ << "</resources>" << std::endl;
+ std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
+ XliffXmlPullParser parser(sourceParser);
+ EXPECT_EQ(XmlPullParser::Event::kStartDocument, parser.getEvent());
+
+ EXPECT_EQ(XmlPullParser::Event::kStartNamespace, parser.next());
+ EXPECT_EQ(parser.getNamespaceUri(), u"urn:oasis:names:tc:xliff:document:1.2");
+ EXPECT_EQ(parser.getNamespacePrefix(), u"xliff");
+
+ EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.next());
+ EXPECT_EQ(parser.getElementNamespace(), u"");
+ EXPECT_EQ(parser.getElementName(), u"resources");
+ EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); // Account for newline/whitespace.
+
+ EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.next());
+ EXPECT_EQ(parser.getElementNamespace(), u"");
+ EXPECT_EQ(parser.getElementName(), u"string");
+
+ EXPECT_EQ(XmlPullParser::Event::kText, parser.next());
+ EXPECT_EQ(parser.getText(), u"Hey ");
+
+ EXPECT_EQ(XmlPullParser::Event::kText, parser.next());
+ EXPECT_EQ(parser.getText(), u"there");
+
+ EXPECT_EQ(XmlPullParser::Event::kText, parser.next());
+ EXPECT_EQ(parser.getText(), u" world");
+
+ EXPECT_EQ(XmlPullParser::Event::kEndElement, parser.next());
+ EXPECT_EQ(parser.getElementNamespace(), u"");
+ EXPECT_EQ(parser.getElementName(), u"string");
+ EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); // Account for newline/whitespace.
+
+ EXPECT_EQ(XmlPullParser::Event::kEndElement, parser.next());
+ EXPECT_EQ(parser.getElementNamespace(), u"");
+ EXPECT_EQ(parser.getElementName(), u"resources");
+
+ EXPECT_EQ(XmlPullParser::Event::kEndNamespace, parser.next());
+ EXPECT_EQ(parser.getNamespacePrefix(), u"xliff");
+ EXPECT_EQ(parser.getNamespaceUri(), u"urn:oasis:names:tc:xliff:document:1.2");
+
+ EXPECT_EQ(XmlPullParser::Event::kEndDocument, parser.next());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/XmlDom.cpp b/tools/aapt2/XmlDom.cpp
new file mode 100644
index 0000000..763029f
--- /dev/null
+++ b/tools/aapt2/XmlDom.cpp
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Logger.h"
+#include "Util.h"
+#include "XmlDom.h"
+#include "XmlPullParser.h"
+
+#include <cassert>
+#include <memory>
+#include <stack>
+#include <string>
+#include <tuple>
+
+namespace aapt {
+namespace xml {
+
+constexpr char kXmlNamespaceSep = 1;
+
+struct Stack {
+ std::unique_ptr<xml::Node> root;
+ std::stack<xml::Node*> nodeStack;
+ std::u16string pendingComment;
+};
+
+/**
+ * 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 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 (thisNode->type != NodeType::kText) {
+ stack->nodeStack.push(thisNode);
+ }
+}
+
+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));
+
+ std::unique_ptr<Namespace> ns = util::make_unique<Namespace>();
+ if (prefix) {
+ ns->namespacePrefix = util::utf8ToUtf16(prefix);
+ }
+
+ if (uri) {
+ ns->namespaceUri = util::utf8ToUtf16(uri);
+ }
+
+ 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));
+
+ assert(!stack->nodeStack.empty());
+ stack->nodeStack.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 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));
+
+ std::unique_ptr<Element> el = util::make_unique<Element>();
+ splitName(name, &el->namespaceUri, &el->name);
+
+ while (*attrs) {
+ Attribute attribute;
+ splitName(*attrs++, &attribute.namespaceUri, &attribute.name);
+ attribute.value = util::utf8ToUtf16(*attrs++);
+
+ // Insert in sorted order.
+ auto iter = std::lower_bound(el->attributes.begin(), el->attributes.end(), attribute,
+ lessAttribute);
+ el->attributes.insert(iter, std::move(attribute));
+ }
+
+ el->comment = std::move(stack->pendingComment);
+ 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));
+
+ assert(!stack->nodeStack.empty());
+ stack->nodeStack.top()->comment = std::move(stack->pendingComment);
+ stack->nodeStack.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) {
+ 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 (lastChild->type == NodeType::kText) {
+ Text* text = static_cast<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));
+}
+
+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));
+
+ if (!stack->pendingComment.empty()) {
+ stack->pendingComment += '\n';
+ }
+ stack->pendingComment += util::utf8ToUtf16(comment);
+}
+
+std::unique_ptr<Node> inflate(std::istream* in, SourceLogger* logger) {
+ 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 = {};
+ logger->error() << strerror(errno) << std::endl;
+ break;
+ }
+
+ if (XML_Parse(parser, buffer, in->gcount(), in->eof()) == XML_STATUS_ERROR) {
+ stack.root = {};
+ logger->error(XML_GetCurrentLineNumber(parser))
+ << XML_ErrorString(XML_GetErrorCode(parser)) << std::endl;
+ break;
+ }
+ }
+
+ XML_ParserFree(parser);
+ return std::move(stack.root);
+}
+
+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));
+ }
+ }
+}
+
+std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* logger) {
+ std::unique_ptr<Node> root;
+ std::stack<Node*> nodeStack;
+
+ android::ResXMLTree tree;
+ if (tree.setTo(data, dataLen) != android::NO_ERROR) {
+ return {};
+ }
+
+ android::ResXMLParser::event_code_t code;
+ while ((code = tree.next()) != android::ResXMLParser::BAD_DOCUMENT &&
+ code != android::ResXMLParser::END_DOCUMENT) {
+ std::unique_ptr<Node> newNode;
+ switch (code) {
+ case android::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 android::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 android::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 android::ResXMLParser::END_NAMESPACE:
+ case android::ResXMLParser::END_TAG:
+ assert(!nodeStack.empty());
+ nodeStack.pop();
+ break;
+
+ default:
+ assert(false);
+ break;
+ }
+
+ 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 (thisNode->type != NodeType::kText) {
+ nodeStack.push(thisNode);
+ }
+ }
+ }
+ return std::move(root);
+}
+
+Node::Node(NodeType type) : type(type), parent(nullptr), lineNumber(0), columnNumber(0) {
+}
+
+void Node::addChild(std::unique_ptr<Node> child) {
+ child->parent = this;
+ children.push_back(std::move(child));
+}
+
+Namespace::Namespace() : BaseNode(NodeType::kNamespace) {
+}
+
+std::unique_ptr<Node> Namespace::clone() const {
+ Namespace* ns = new Namespace();
+ ns->lineNumber = lineNumber;
+ ns->columnNumber = columnNumber;
+ ns->comment = comment;
+ ns->namespacePrefix = namespacePrefix;
+ ns->namespaceUri = namespaceUri;
+ for (auto& child : children) {
+ ns->addChild(child->clone());
+ }
+ return std::unique_ptr<Node>(ns);
+}
+
+Element::Element() : BaseNode(NodeType::kElement) {
+}
+
+std::unique_ptr<Node> Element::clone() const {
+ Element* el = new Element();
+ el->lineNumber = lineNumber;
+ el->columnNumber = columnNumber;
+ el->comment = comment;
+ el->namespaceUri = namespaceUri;
+ el->name = name;
+ el->attributes = attributes;
+ for (auto& child : children) {
+ el->addChild(child->clone());
+ }
+ return std::unique_ptr<Node>(el);
+}
+
+Attribute* Element::findAttribute(const StringPiece16& ns, const StringPiece16& name) {
+ for (auto& attr : attributes) {
+ if (ns == attr.namespaceUri && name == attr.name) {
+ return &attr;
+ }
+ }
+ return nullptr;
+}
+
+Element* Element::findChild(const StringPiece16& ns, const StringPiece16& name) {
+ return findChildWithAttribute(ns, name, nullptr);
+}
+
+Element* Element::findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name,
+ const Attribute* reqAttr) {
+ for (auto& childNode : children) {
+ Node* child = childNode.get();
+ while (child->type == NodeType::kNamespace) {
+ if (child->children.empty()) {
+ break;
+ }
+ child = child->children[0].get();
+ }
+
+ if (child->type == NodeType::kElement) {
+ Element* el = static_cast<Element*>(child);
+ if (ns == el->namespaceUri && name == el->name) {
+ if (!reqAttr) {
+ return el;
+ }
+
+ Attribute* attrName = el->findAttribute(reqAttr->namespaceUri, reqAttr->name);
+ if (attrName && attrName->value == reqAttr->value) {
+ return el;
+ }
+ }
+ }
+ }
+ return nullptr;
+}
+
+std::vector<Element*> Element::getChildElements() {
+ std::vector<Element*> elements;
+ for (auto& childNode : children) {
+ Node* child = childNode.get();
+ while (child->type == NodeType::kNamespace) {
+ if (child->children.empty()) {
+ break;
+ }
+ child = child->children[0].get();
+ }
+
+ if (child->type == NodeType::kElement) {
+ elements.push_back(static_cast<Element*>(child));
+ }
+ }
+ return elements;
+}
+
+Text::Text() : BaseNode(NodeType::kText) {
+}
+
+std::unique_ptr<Node> Text::clone() const {
+ Text* el = new Text();
+ el->lineNumber = lineNumber;
+ el->columnNumber = columnNumber;
+ el->comment = comment;
+ el->text = text;
+ return std::unique_ptr<Node>(el);
+}
+
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/XmlDom.h
new file mode 100644
index 0000000..6931884
--- /dev/null
+++ b/tools/aapt2/XmlDom.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_XML_DOM_H
+#define AAPT_XML_DOM_H
+
+#include "Logger.h"
+#include "StringPiece.h"
+
+#include <istream>
+#include <libexpat/expat.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace aapt {
+namespace xml {
+
+struct Visitor;
+
+/**
+ * The type of node. Can be used to downcast to the concrete XML node
+ * class.
+ */
+enum class NodeType {
+ kNamespace,
+ kElement,
+ kText,
+};
+
+/**
+ * Base class for all XML nodes.
+ */
+struct Node {
+ NodeType type;
+ Node* parent;
+ size_t lineNumber;
+ size_t columnNumber;
+ std::u16string comment;
+ std::vector<std::unique_ptr<Node>> children;
+
+ Node(NodeType type);
+ void addChild(std::unique_ptr<Node> child);
+ virtual std::unique_ptr<Node> clone() const = 0;
+ virtual void accept(Visitor* visitor) = 0;
+ virtual ~Node() {}
+};
+
+/**
+ * Base class that implements the visitor methods for a
+ * subclass of Node.
+ */
+template <typename Derived>
+struct BaseNode : public Node {
+ BaseNode(NodeType t);
+ virtual void accept(Visitor* visitor) override;
+};
+
+/**
+ * A Namespace XML node. Can only have one child.
+ */
+struct Namespace : public BaseNode<Namespace> {
+ std::u16string namespacePrefix;
+ std::u16string namespaceUri;
+
+ Namespace();
+ virtual std::unique_ptr<Node> clone() const override;
+};
+
+/**
+ * An XML attribute.
+ */
+struct Attribute {
+ std::u16string namespaceUri;
+ std::u16string name;
+ std::u16string value;
+};
+
+/**
+ * An Element XML node.
+ */
+struct Element : public BaseNode<Element> {
+ std::u16string namespaceUri;
+ std::u16string name;
+ std::vector<Attribute> attributes;
+
+ Element();
+ virtual std::unique_ptr<Node> clone() const override;
+ Attribute* findAttribute(const StringPiece16& ns, const StringPiece16& name);
+ xml::Element* findChild(const StringPiece16& ns, const StringPiece16& name);
+ xml::Element* findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name,
+ const xml::Attribute* reqAttr);
+ std::vector<xml::Element*> getChildElements();
+};
+
+/**
+ * A Text (CDATA) XML node. Can not have any children.
+ */
+struct Text : public BaseNode<Text> {
+ std::u16string text;
+
+ Text();
+ virtual std::unique_ptr<Node> clone() const override;
+};
+
+/**
+ * Inflates an XML DOM from a text stream, logging errors to the logger.
+ * Returns the root node on success, or nullptr on failure.
+ */
+std::unique_ptr<Node> inflate(std::istream* in, SourceLogger* logger);
+
+/**
+ * Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger.
+ * Returns the root node on success, or nullptr on failure.
+ */
+std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* logger);
+
+/**
+ * A visitor interface for the different XML Node subtypes.
+ */
+struct Visitor {
+ virtual void visit(Namespace* node) = 0;
+ virtual void visit(Element* node) = 0;
+ virtual void visit(Text* text) = 0;
+};
+
+// Implementations
+
+template <typename Derived>
+BaseNode<Derived>::BaseNode(NodeType type) : Node(type) {
+}
+
+template <typename Derived>
+void BaseNode<Derived>::accept(Visitor* visitor) {
+ visitor->visit(static_cast<Derived*>(this));
+}
+
+} // namespace xml
+} // namespace aapt
+
+#endif // AAPT_XML_DOM_H
diff --git a/tools/aapt2/XmlDom_test.cpp b/tools/aapt2/XmlDom_test.cpp
new file mode 100644
index 0000000..0217144
--- /dev/null
+++ b/tools/aapt2/XmlDom_test.cpp
@@ -0,0 +1,49 @@
+/*
+ * 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 "XmlDom.h"
+
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+
+TEST(XmlDomTest, Inflate) {
+ 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">
+ <TextView android:id="@+id/id"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </Layout>
+ )EOF";
+
+ SourceLogger logger(Source{ "/test/path" });
+ std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
+ ASSERT_NE(root, nullptr);
+
+ EXPECT_EQ(root->type, xml::NodeType::kNamespace);
+ xml::Namespace* ns = static_cast<xml::Namespace*>(root.get());
+ EXPECT_EQ(ns->namespaceUri, u"http://schemas.android.com/apk/res/android");
+ EXPECT_EQ(ns->namespacePrefix, u"android");
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/XmlFlattener.cpp b/tools/aapt2/XmlFlattener.cpp
new file mode 100644
index 0000000..56b5613
--- /dev/null
+++ b/tools/aapt2/XmlFlattener.cpp
@@ -0,0 +1,574 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BigBuffer.h"
+#include "Logger.h"
+#include "Maybe.h"
+#include "Resolver.h"
+#include "Resource.h"
+#include "ResourceParser.h"
+#include "ResourceValues.h"
+#include "SdkConstants.h"
+#include "Source.h"
+#include "StringPool.h"
+#include "Util.h"
+#include "XmlFlattener.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <limits>
+#include <map>
+#include <string>
+#include <vector>
+
+namespace aapt {
+namespace xml {
+
+constexpr uint32_t kLowPriority = 0xffffffffu;
+
+// A vector that maps String refs to their final destination in the out buffer.
+using FlatStringRefList = std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>;
+
+struct XmlFlattener : public Visitor {
+ XmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs,
+ const std::u16string& defaultPackage) :
+ mOut(outBuffer), mPool(pool), mStringRefs(stringRefs),
+ mDefaultPackage(defaultPackage) {
+ }
+
+ // No copying.
+ XmlFlattener(const XmlFlattener&) = delete;
+ XmlFlattener& operator=(const XmlFlattener&) = delete;
+
+ void writeNamespace(Namespace* node, uint16_t type) {
+ const size_t startIndex = mOut->size();
+ android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
+ android::ResXMLTree_namespaceExt* flatNs =
+ mOut->nextBlock<android::ResXMLTree_namespaceExt>();
+ mOut->align4();
+
+ flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) };
+ flatNode->lineNumber = node->lineNumber;
+ flatNode->comment.index = -1;
+ addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
+ addString(node->namespaceUri, kLowPriority, &flatNs->uri);
+ }
+
+ virtual void visit(Namespace* node) override {
+ // Extract the package/prefix from this namespace node.
+ Maybe<std::u16string> package = util::extractPackageFromNamespace(node->namespaceUri);
+ if (package) {
+ mPackageAliases.emplace_back(
+ node->namespacePrefix,
+ package.value().empty() ? mDefaultPackage : package.value());
+ }
+
+ writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
+ for (const auto& child : node->children) {
+ child->accept(this);
+ }
+ writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
+
+ if (package) {
+ mPackageAliases.pop_back();
+ }
+ }
+
+ virtual void visit(Text* node) override {
+ if (util::trimWhitespace(node->text).empty()) {
+ return;
+ }
+
+ const size_t startIndex = mOut->size();
+ android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
+ android::ResXMLTree_cdataExt* flatText = mOut->nextBlock<android::ResXMLTree_cdataExt>();
+ mOut->align4();
+
+ const uint16_t type = android::RES_XML_CDATA_TYPE;
+ flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) };
+ flatNode->lineNumber = node->lineNumber;
+ flatNode->comment.index = -1;
+ addString(node->text, kLowPriority, &flatText->data);
+ }
+
+ virtual void visit(Element* node) override {
+ const size_t startIndex = mOut->size();
+ android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
+ android::ResXMLTree_attrExt* flatElem = mOut->nextBlock<android::ResXMLTree_attrExt>();
+
+ const uint16_t type = android::RES_XML_START_ELEMENT_TYPE;
+ flatNode->header = { type, sizeof(*flatNode), 0 };
+ flatNode->lineNumber = node->lineNumber;
+ flatNode->comment.index = -1;
+
+ addString(node->namespaceUri, kLowPriority, &flatElem->ns);
+ addString(node->name, kLowPriority, &flatElem->name);
+ flatElem->attributeStart = sizeof(*flatElem);
+ flatElem->attributeSize = sizeof(android::ResXMLTree_attribute);
+ flatElem->attributeCount = node->attributes.size();
+
+ if (!writeAttributes(mOut, node, flatElem)) {
+ mError = true;
+ }
+
+ mOut->align4();
+ flatNode->header.size = (uint32_t)(mOut->size() - startIndex);
+
+ for (const auto& child : node->children) {
+ child->accept(this);
+ }
+
+ const size_t startEndIndex = mOut->size();
+ android::ResXMLTree_node* flatEndNode = mOut->nextBlock<android::ResXMLTree_node>();
+ android::ResXMLTree_endElementExt* flatEndElem =
+ mOut->nextBlock<android::ResXMLTree_endElementExt>();
+ mOut->align4();
+
+ const uint16_t endType = android::RES_XML_END_ELEMENT_TYPE;
+ flatEndNode->header = { endType, sizeof(*flatEndNode),
+ (uint32_t)(mOut->size() - startEndIndex) };
+ flatEndNode->lineNumber = node->lineNumber;
+ flatEndNode->comment.index = -1;
+
+ addString(node->namespaceUri, kLowPriority, &flatEndElem->ns);
+ addString(node->name, kLowPriority, &flatEndElem->name);
+ }
+
+ bool success() const {
+ return !mError;
+ }
+
+protected:
+ void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest) {
+ if (!str.empty()) {
+ mStringRefs->emplace_back(mPool->makeRef(str, StringPool::Context{ priority }), dest);
+ } else {
+ // The device doesn't think a string of size 0 is the same as null.
+ dest->index = -1;
+ }
+ }
+
+ void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
+ mStringRefs->emplace_back(ref, dest);
+ }
+
+ Maybe<std::u16string> getPackageAlias(const std::u16string& prefix) {
+ const auto endIter = mPackageAliases.rend();
+ for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
+ if (iter->first == prefix) {
+ return iter->second;
+ }
+ }
+ return {};
+ }
+
+ const std::u16string& getDefaultPackage() const {
+ return mDefaultPackage;
+ }
+
+ /**
+ * Subclasses override this to deal with attributes. Attributes can be flattened as
+ * raw values or as resources.
+ */
+ virtual bool writeAttributes(BigBuffer* out, Element* node,
+ android::ResXMLTree_attrExt* flatElem) = 0;
+
+private:
+ BigBuffer* mOut;
+ StringPool* mPool;
+ FlatStringRefList* mStringRefs;
+ std::u16string mDefaultPackage;
+ bool mError = false;
+ std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
+};
+
+/**
+ * Flattens XML, encoding the attributes as raw strings. This is used in the compile phase.
+ */
+struct CompileXmlFlattener : public XmlFlattener {
+ CompileXmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs,
+ const std::u16string& defaultPackage) :
+ XmlFlattener(outBuffer, pool, stringRefs, defaultPackage) {
+ }
+
+ virtual bool writeAttributes(BigBuffer* out, Element* node,
+ android::ResXMLTree_attrExt* flatElem) override {
+ flatElem->attributeCount = node->attributes.size();
+ if (node->attributes.empty()) {
+ return true;
+ }
+
+ android::ResXMLTree_attribute* flatAttrs = out->nextBlock<android::ResXMLTree_attribute>(
+ node->attributes.size());
+ for (const Attribute& attr : node->attributes) {
+ addString(attr.namespaceUri, kLowPriority, &flatAttrs->ns);
+ addString(attr.name, kLowPriority, &flatAttrs->name);
+ addString(attr.value, kLowPriority, &flatAttrs->rawValue);
+ flatAttrs++;
+ }
+ return true;
+ }
+};
+
+struct AttributeToFlatten {
+ uint32_t resourceId = 0;
+ const Attribute* xmlAttr = nullptr;
+ const ::aapt::Attribute* resourceAttr = nullptr;
+};
+
+static bool lessAttributeId(const AttributeToFlatten& a, uint32_t id) {
+ return a.resourceId < id;
+}
+
+/**
+ * Flattens XML, encoding the attributes as resources.
+ */
+struct LinkedXmlFlattener : public XmlFlattener {
+ LinkedXmlFlattener(BigBuffer* outBuffer, StringPool* pool,
+ std::map<std::u16string, StringPool>* packagePools,
+ FlatStringRefList* stringRefs,
+ const std::u16string& defaultPackage,
+ const std::shared_ptr<IResolver>& resolver,
+ SourceLogger* logger,
+ const FlattenOptions& options) :
+ XmlFlattener(outBuffer, pool, stringRefs, defaultPackage), mResolver(resolver),
+ mLogger(logger), mPackagePools(packagePools), mOptions(options) {
+ }
+
+ virtual bool writeAttributes(BigBuffer* out, Element* node,
+ android::ResXMLTree_attrExt* flatElem) override {
+ bool error = false;
+ std::vector<AttributeToFlatten> sortedAttributes;
+ uint32_t nextAttributeId = 0x80000000u;
+
+ // Sort and filter attributes by their resource ID.
+ for (const Attribute& attr : node->attributes) {
+ AttributeToFlatten attrToFlatten;
+ attrToFlatten.xmlAttr = &attr;
+
+ Maybe<std::u16string> package = util::extractPackageFromNamespace(attr.namespaceUri);
+ if (package) {
+ // Find the Attribute object via our Resolver.
+ ResourceName attrName = { package.value(), ResourceType::kAttr, attr.name };
+ if (attrName.package.empty()) {
+ attrName.package = getDefaultPackage();
+ }
+
+ Maybe<IResolver::Entry> result = mResolver->findAttribute(attrName);
+ if (!result || !result.value().id.isValid() || !result.value().attr) {
+ error = true;
+ mLogger->error(node->lineNumber)
+ << "unresolved attribute '" << attrName << "'."
+ << std::endl;
+ } else {
+ attrToFlatten.resourceId = result.value().id.id;
+ attrToFlatten.resourceAttr = result.value().attr;
+
+ size_t sdk = findAttributeSdkLevel(attrToFlatten.resourceId);
+ if (mOptions.maxSdkAttribute && sdk > mOptions.maxSdkAttribute.value()) {
+ // We need to filter this attribute out.
+ mSmallestFilteredSdk = std::min(mSmallestFilteredSdk, sdk);
+ continue;
+ }
+ }
+ }
+
+ if (attrToFlatten.resourceId == 0) {
+ // Attributes that have no resource ID (because they don't belong to a
+ // package) should appear after those that do have resource IDs. Assign
+ // them some integer value that will appear after.
+ attrToFlatten.resourceId = nextAttributeId++;
+ }
+
+ // Insert the attribute into the sorted vector.
+ auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(),
+ attrToFlatten.resourceId, lessAttributeId);
+ sortedAttributes.insert(iter, std::move(attrToFlatten));
+ }
+
+ flatElem->attributeCount = sortedAttributes.size();
+ if (sortedAttributes.empty()) {
+ return true;
+ }
+
+ android::ResXMLTree_attribute* flatAttr = out->nextBlock<android::ResXMLTree_attribute>(
+ sortedAttributes.size());
+
+ // Now that we have sorted the attributes into their final encoded order, it's time
+ // to actually write them out.
+ uint16_t attributeIndex = 1;
+ for (const AttributeToFlatten& attrToFlatten : sortedAttributes) {
+ Maybe<std::u16string> package = util::extractPackageFromNamespace(
+ attrToFlatten.xmlAttr->namespaceUri);
+
+ // Assign the indices for specific attributes.
+ if (package && package.value() == u"android" && attrToFlatten.xmlAttr->name == u"id") {
+ flatElem->idIndex = attributeIndex;
+ } else if (attrToFlatten.xmlAttr->namespaceUri.empty()) {
+ if (attrToFlatten.xmlAttr->name == u"class") {
+ flatElem->classIndex = attributeIndex;
+ } else if (attrToFlatten.xmlAttr->name == u"style") {
+ flatElem->styleIndex = attributeIndex;
+ }
+ }
+ attributeIndex++;
+
+ // Add the namespaceUri and name to the list of StringRefs to encode.
+ addString(attrToFlatten.xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns);
+ flatAttr->rawValue.index = -1;
+
+ if (!attrToFlatten.resourceAttr) {
+ addString(attrToFlatten.xmlAttr->name, kLowPriority, &flatAttr->name);
+ } else {
+ // We've already extracted the package successfully before.
+ assert(package);
+
+ // Attribute names are stored without packages, but we use
+ // their StringPool index to lookup their resource IDs.
+ // This will cause collisions, so we can't dedupe
+ // attribute names from different packages. We use separate
+ // pools that we later combine.
+ //
+ // Lookup the StringPool for this package and make the reference there.
+ StringPool::Ref nameRef = (*mPackagePools)[package.value()].makeRef(
+ attrToFlatten.xmlAttr->name,
+ StringPool::Context{ attrToFlatten.resourceId });
+
+ // Add it to the list of strings to flatten.
+ addString(nameRef, &flatAttr->name);
+
+ if (mOptions.keepRawValues) {
+ // Keep raw values (this is for static libraries).
+ // TODO(with a smarter inflater for binary XML, we can do without this).
+ addString(attrToFlatten.xmlAttr->value, kLowPriority, &flatAttr->rawValue);
+ }
+ }
+
+ error |= !flattenItem(node, attrToFlatten.xmlAttr->value, attrToFlatten.resourceAttr,
+ flatAttr);
+ flatAttr->typedValue.size = sizeof(flatAttr->typedValue);
+ flatAttr++;
+ }
+ return !error;
+ }
+
+ Maybe<size_t> getSmallestFilteredSdk() const {
+ if (mSmallestFilteredSdk == std::numeric_limits<size_t>::max()) {
+ return {};
+ }
+ return mSmallestFilteredSdk;
+ }
+
+private:
+ bool flattenItem(const Node* el, const std::u16string& value, const ::aapt::Attribute* attr,
+ android::ResXMLTree_attribute* flatAttr) {
+ std::unique_ptr<Item> item;
+ if (!attr) {
+ bool create = false;
+ item = ResourceParser::tryParseReference(value, &create);
+ if (!item) {
+ flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
+ addString(value, kLowPriority, &flatAttr->rawValue);
+ addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>(
+ &flatAttr->typedValue.data));
+ return true;
+ }
+ } else {
+ item = ResourceParser::parseItemForAttribute(value, *attr);
+ if (!item) {
+ if (!(attr->typeMask & android::ResTable_map::TYPE_STRING)) {
+ mLogger->error(el->lineNumber)
+ << "'"
+ << value
+ << "' is not compatible with attribute '"
+ << *attr
+ << "'."
+ << std::endl;
+ return false;
+ }
+
+ flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
+ addString(value, kLowPriority, &flatAttr->rawValue);
+ addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>(
+ &flatAttr->typedValue.data));
+ return true;
+ }
+ }
+
+ assert(item);
+
+ bool error = false;
+
+ // If this is a reference, resolve the name into an ID.
+ visitFunc<Reference>(*item, [&](Reference& reference) {
+ // First see if we can convert the package name from a prefix to a real
+ // package name.
+ ResourceName realName = reference.name;
+ if (!realName.package.empty()) {
+ Maybe<std::u16string> package = getPackageAlias(realName.package);
+ if (package) {
+ realName.package = package.value();
+ }
+ } else {
+ realName.package = getDefaultPackage();
+ }
+
+ Maybe<ResourceId> result = mResolver->findId(realName);
+ if (!result || !result.value().isValid()) {
+ std::ostream& out = mLogger->error(el->lineNumber)
+ << "unresolved reference '"
+ << reference.name
+ << "'";
+ if (realName != reference.name) {
+ out << " (aka '" << realName << "')";
+ }
+ out << "'." << std::endl;
+ error = true;
+ } else {
+ reference.id = result.value();
+ }
+ });
+
+ if (error) {
+ return false;
+ }
+
+ item->flatten(flatAttr->typedValue);
+ return true;
+ }
+
+ std::shared_ptr<IResolver> mResolver;
+ SourceLogger* mLogger;
+ std::map<std::u16string, StringPool>* mPackagePools;
+ FlattenOptions mOptions;
+ size_t mSmallestFilteredSdk = std::numeric_limits<size_t>::max();
+};
+
+/**
+ * The binary XML file expects the StringPool to appear first, but we haven't collected the
+ * strings yet. We write to a temporary BigBuffer while parsing the input, adding strings
+ * we encounter to the StringPool. At the end, we write the StringPool to the given BigBuffer and
+ * then move the data from the temporary BigBuffer into the given one. This incurs no
+ * copies as the given BigBuffer simply takes ownership of the data.
+ */
+static void flattenXml(StringPool* pool, FlatStringRefList* stringRefs, BigBuffer* outBuffer,
+ BigBuffer&& xmlTreeBuffer) {
+ // Sort the string pool so that attribute resource IDs show up first.
+ pool->sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+ return a.context.priority < b.context.priority;
+ });
+
+ // Now we flatten the string pool references into the correct places.
+ for (const auto& refEntry : *stringRefs) {
+ refEntry.second->index = refEntry.first.getIndex();
+ }
+
+ // Write the XML header.
+ const size_t beforeXmlTreeIndex = outBuffer->size();
+ android::ResXMLTree_header* header = outBuffer->nextBlock<android::ResXMLTree_header>();
+ header->header.type = android::RES_XML_TYPE;
+ header->header.headerSize = sizeof(*header);
+
+ // Flatten the StringPool.
+ StringPool::flattenUtf16(outBuffer, *pool);
+
+ // Write the array of resource IDs, indexed by StringPool order.
+ const size_t beforeResIdMapIndex = outBuffer->size();
+ android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>();
+ resIdMapChunk->type = android::RES_XML_RESOURCE_MAP_TYPE;
+ resIdMapChunk->headerSize = sizeof(*resIdMapChunk);
+ for (const auto& str : *pool) {
+ ResourceId id { str->context.priority };
+ if (id.id == kLowPriority || !id.isValid()) {
+ // When we see the first non-resource ID,
+ // we're done.
+ break;
+ }
+
+ *outBuffer->nextBlock<uint32_t>() = id.id;
+ }
+ resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex;
+
+ // Move the temporary BigBuffer into outBuffer.
+ outBuffer->appendBuffer(std::move(xmlTreeBuffer));
+ header->header.size = outBuffer->size() - beforeXmlTreeIndex;
+}
+
+bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer) {
+ StringPool pool;
+
+ // This will hold the StringRefs and the location in which to write the index.
+ // Once we sort the StringPool, we can assign the updated indices
+ // to the correct data locations.
+ FlatStringRefList stringRefs;
+
+ // Since we don't know the size of the final StringPool, we write to this
+ // temporary BigBuffer, which we will append to outBuffer later.
+ BigBuffer out(1024);
+
+ CompileXmlFlattener flattener(&out, &pool, &stringRefs, defaultPackage);
+ root->accept(&flattener);
+
+ if (!flattener.success()) {
+ return false;
+ }
+
+ flattenXml(&pool, &stringRefs, outBuffer, std::move(out));
+ return true;
+};
+
+Maybe<size_t> flattenAndLink(const Source& source, Node* root,
+ const std::u16string& defaultPackage,
+ const std::shared_ptr<IResolver>& resolver,
+ const FlattenOptions& options, BigBuffer* outBuffer) {
+ SourceLogger logger(source);
+ StringPool pool;
+
+ // Attribute names are stored without packages, but we use
+ // their StringPool index to lookup their resource IDs.
+ // This will cause collisions, so we can't dedupe
+ // attribute names from different packages. We use separate
+ // pools that we later combine.
+ std::map<std::u16string, StringPool> packagePools;
+
+ FlatStringRefList stringRefs;
+
+ // Since we don't know the size of the final StringPool, we write to this
+ // temporary BigBuffer, which we will append to outBuffer later.
+ BigBuffer out(1024);
+
+ LinkedXmlFlattener flattener(&out, &pool, &packagePools, &stringRefs, defaultPackage, resolver,
+ &logger, options);
+ root->accept(&flattener);
+
+ if (!flattener.success()) {
+ return {};
+ }
+
+ // Merge the package pools into the main pool.
+ for (auto& packagePoolEntry : packagePools) {
+ pool.merge(std::move(packagePoolEntry.second));
+ }
+
+ flattenXml(&pool, &stringRefs, outBuffer, std::move(out));
+
+ if (flattener.getSmallestFilteredSdk()) {
+ return flattener.getSmallestFilteredSdk();
+ }
+ return 0;
+}
+
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/XmlFlattener.h b/tools/aapt2/XmlFlattener.h
new file mode 100644
index 0000000..4ece0a3
--- /dev/null
+++ b/tools/aapt2/XmlFlattener.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_XML_FLATTENER_H
+#define AAPT_XML_FLATTENER_H
+
+#include "BigBuffer.h"
+#include "Maybe.h"
+#include "Resolver.h"
+#include "Source.h"
+#include "XmlDom.h"
+
+#include <string>
+
+namespace aapt {
+namespace xml {
+
+/**
+ * Flattens an XML file into a binary representation parseable by
+ * the Android resource system.
+ */
+bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer);
+
+/**
+ * Options for flattenAndLink.
+ */
+struct FlattenOptions {
+ /**
+ * Keep attribute raw string values along with typed values.
+ */
+ bool keepRawValues = false;
+
+ /**
+ * If set, any attribute introduced in a later SDK will not be encoded.
+ */
+ Maybe<size_t> maxSdkAttribute;
+};
+
+/**
+ * Like flatten(Node*,BigBuffer*), but references to resources are checked
+ * and string values are transformed to typed data where possible.
+ *
+ * `defaultPackage` is used when a reference has no package or the namespace URI
+ * "http://schemas.android.com/apk/res-auto" is used.
+ *
+ * `resolver` is used to resolve references to resources.
+ */
+Maybe<size_t> flattenAndLink(const Source& source, Node* root,
+ const std::u16string& defaultPackage,
+ const std::shared_ptr<IResolver>& resolver,
+ const FlattenOptions& options, BigBuffer* outBuffer);
+
+} // namespace xml
+} // namespace aapt
+
+#endif // AAPT_XML_FLATTENER_H
diff --git a/tools/aapt2/XmlFlattener_test.cpp b/tools/aapt2/XmlFlattener_test.cpp
new file mode 100644
index 0000000..8915d24
--- /dev/null
+++ b/tools/aapt2/XmlFlattener_test.cpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MockResolver.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "Util.h"
+#include "XmlFlattener.h"
+
+#include <androidfw/AssetManager.h>
+#include <androidfw/ResourceTypes.h>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+using namespace android;
+
+namespace aapt {
+namespace xml {
+
+constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+
+class XmlFlattenerTest : public ::testing::Test {
+public:
+ virtual void SetUp() override {
+ mResolver = std::make_shared<MockResolver>(
+ std::make_shared<ResourceTable>(),
+ std::map<ResourceName, ResourceId>({
+ { ResourceName{ u"android", ResourceType::kAttr, u"attr" },
+ ResourceId{ 0x01010000u } },
+ { ResourceName{ u"android", ResourceType::kId, u"id" },
+ ResourceId{ 0x01020000u } },
+ { ResourceName{ u"com.lib", ResourceType::kAttr, u"attr" },
+ ResourceId{ 0x01010001u } },
+ { ResourceName{ u"com.lib", ResourceType::kId, u"id" },
+ ResourceId{ 0x01020001u } }}));
+ }
+
+ ::testing::AssertionResult testFlatten(const std::string& in, ResXMLTree* outTree) {
+ std::stringstream input(kXmlPreamble);
+ input << in << std::endl;
+
+ SourceLogger logger(Source{ "test.xml" });
+ std::unique_ptr<Node> root = inflate(&input, &logger);
+ if (!root) {
+ return ::testing::AssertionFailure();
+ }
+
+ BigBuffer outBuffer(1024);
+ if (!flattenAndLink(Source{ "test.xml" }, root.get(), std::u16string(u"android"),
+ mResolver, {}, &outBuffer)) {
+ return ::testing::AssertionFailure();
+ }
+
+ std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
+ if (outTree->setTo(data.get(), outBuffer.size(), true) != NO_ERROR) {
+ return ::testing::AssertionFailure();
+ }
+ return ::testing::AssertionSuccess();
+ }
+
+ std::shared_ptr<IResolver> mResolver;
+};
+
+TEST_F(XmlFlattenerTest, ParseSimpleView) {
+ std::string input = R"EOF(
+ <View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:attr="@id/id"
+ class="str"
+ style="@id/id">
+ </View>
+ )EOF";
+ ResXMLTree tree;
+ ASSERT_TRUE(testFlatten(input, &tree));
+
+ while (tree.next() != ResXMLTree::START_TAG) {
+ ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
+ ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
+ }
+
+ const StringPiece16 androidNs = u"http://schemas.android.com/apk/res/android";
+ const StringPiece16 attrName = u"attr";
+ ssize_t idx = tree.indexOfAttribute(androidNs.data(), androidNs.size(), attrName.data(),
+ attrName.size());
+ ASSERT_GE(idx, 0);
+ EXPECT_EQ(tree.getAttributeNameResID(idx), 0x01010000u);
+ EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_REFERENCE);
+
+ const StringPiece16 class16 = u"class";
+ idx = tree.indexOfAttribute(nullptr, 0, class16.data(), class16.size());
+ ASSERT_GE(idx, 0);
+ EXPECT_EQ(tree.getAttributeNameResID(idx), 0u);
+ EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_STRING);
+ EXPECT_EQ(tree.getAttributeData(idx), tree.getAttributeValueStringID(idx));
+
+ const StringPiece16 style16 = u"style";
+ idx = tree.indexOfAttribute(nullptr, 0, style16.data(), style16.size());
+ ASSERT_GE(idx, 0);
+ EXPECT_EQ(tree.getAttributeNameResID(idx), 0u);
+ EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_REFERENCE);
+ EXPECT_EQ((uint32_t) tree.getAttributeData(idx), 0x01020000u);
+ EXPECT_EQ(tree.getAttributeValueStringID(idx), -1);
+
+ while (tree.next() != ResXMLTree::END_DOCUMENT) {
+ ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
+ }
+}
+
+TEST_F(XmlFlattenerTest, ParseViewWithPackageAlias) {
+ std::string input = "<View xmlns:ns1=\"http://schemas.android.com/apk/res/android\"\n"
+ " xmlns:ns2=\"http://schemas.android.com/apk/res/android\"\n"
+ " ns1:attr=\"@ns2:id/id\">\n"
+ "</View>";
+ ResXMLTree tree;
+ ASSERT_TRUE(testFlatten(input, &tree));
+
+ while (tree.next() != ResXMLTree::END_DOCUMENT) {
+ ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
+ }
+}
+
+::testing::AssertionResult attributeNameAndValueEquals(ResXMLTree* tree, size_t index,
+ ResourceId nameId, ResourceId valueId) {
+ if (index >= tree->getAttributeCount()) {
+ return ::testing::AssertionFailure() << "index " << index << " is out of bounds ("
+ << tree->getAttributeCount() << ")";
+ }
+
+ if (tree->getAttributeNameResID(index) != nameId.id) {
+ return ::testing::AssertionFailure()
+ << "attribute at index " << index << " has ID "
+ << ResourceId{ (uint32_t) tree->getAttributeNameResID(index) }
+ << ". Expected ID " << nameId;
+ }
+
+ if (tree->getAttributeDataType(index) != Res_value::TYPE_REFERENCE) {
+ return ::testing::AssertionFailure() << "attribute at index " << index << " has value of "
+ << "type " << std::hex
+ << tree->getAttributeDataType(index) << std::dec
+ << ". Expected reference (" << std::hex
+ << Res_value::TYPE_REFERENCE << std::dec << ")";
+ }
+
+ if ((uint32_t) tree->getAttributeData(index) != valueId.id) {
+ return ::testing::AssertionFailure()
+ << "attribute at index " << index << " has value " << "with ID "
+ << ResourceId{ (uint32_t) tree->getAttributeData(index) }
+ << ". Expected ID " << valueId;
+ }
+ return ::testing::AssertionSuccess();
+}
+
+TEST_F(XmlFlattenerTest, ParseViewWithShadowedPackageAlias) {
+ std::string input = "<View xmlns:app=\"http://schemas.android.com/apk/res/android\"\n"
+ " app:attr=\"@app:id/id\">\n"
+ " <View xmlns:app=\"http://schemas.android.com/apk/res/com.lib\"\n"
+ " app:attr=\"@app:id/id\"/>\n"
+ "</View>";
+ ResXMLTree tree;
+ ASSERT_TRUE(testFlatten(input, &tree));
+
+ while (tree.next() != ResXMLTree::START_TAG) {
+ ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
+ ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
+ }
+
+ ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010000u },
+ ResourceId{ 0x01020000u }));
+
+ while (tree.next() != ResXMLTree::START_TAG) {
+ ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
+ ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
+ }
+
+ ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u },
+ ResourceId{ 0x01020001u }));
+}
+
+TEST_F(XmlFlattenerTest, ParseViewWithLocalPackageAndAliasOfTheSameName) {
+ std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/com.lib\"\n"
+ " android:attr=\"@id/id\"/>";
+ ResXMLTree tree;
+ ASSERT_TRUE(testFlatten(input, &tree));
+
+ while (tree.next() != ResXMLTree::START_TAG) {
+ ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
+ ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
+ }
+
+ // We expect the 'android:attr' to be converted to 'com.lib:attr' due to the namespace
+ // assignment.
+ // However, we didn't give '@id/id' a package, so it should use the default package
+ // 'android', and not be converted from 'android' to 'com.lib'.
+ ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u },
+ ResourceId{ 0x01020000u }));
+}
+
+/*
+ * The device ResXMLParser in libandroidfw differentiates between empty namespace and null
+ * namespace.
+ */
+TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
+ std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " package=\"android\"/>";
+
+ ResXMLTree tree;
+ ASSERT_TRUE(testFlatten(input, &tree));
+
+ while (tree.next() != ResXMLTree::START_TAG) {
+ ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
+ ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
+ }
+
+ const StringPiece16 kPackage = u"package";
+ EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0);
+}
+
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/XmlPullParser.h b/tools/aapt2/XmlPullParser.h
new file mode 100644
index 0000000..accfd30
--- /dev/null
+++ b/tools/aapt2/XmlPullParser.h
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_XML_PULL_PARSER_H
+#define AAPT_XML_PULL_PARSER_H
+
+#include <algorithm>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include "StringPiece.h"
+
+namespace aapt {
+
+class XmlPullParser {
+public:
+ enum class Event {
+ kBadDocument,
+ kStartDocument,
+ kEndDocument,
+
+ kStartNamespace,
+ kEndNamespace,
+ kStartElement,
+ kEndElement,
+ kText,
+ kComment,
+ };
+
+ static void skipCurrentElement(XmlPullParser* parser);
+ static bool isGoodEvent(Event event);
+
+ virtual ~XmlPullParser() {}
+
+ /**
+ * Returns the current event that is being processed.
+ */
+ virtual Event getEvent() const = 0;
+
+ virtual const std::string& getLastError() const = 0;
+
+ /**
+ * Note, unlike XmlPullParser, the first call to next() will return
+ * StartElement of the first element.
+ */
+ virtual Event next() = 0;
+
+ //
+ // These are available for all nodes.
+ //
+
+ virtual const std::u16string& getComment() const = 0;
+ virtual size_t getLineNumber() const = 0;
+ virtual size_t getDepth() const = 0;
+
+ /**
+ * Returns the character data for a Text event.
+ */
+ virtual const std::u16string& getText() const = 0;
+
+ //
+ // Namespace prefix and URI are available for StartNamespace and EndNamespace.
+ //
+
+ virtual const std::u16string& getNamespacePrefix() const = 0;
+ virtual const std::u16string& getNamespaceUri() const = 0;
+
+ /*
+ * 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'.
+ */
+ virtual bool applyPackageAlias(std::u16string* package,
+ const std::u16string& defaultPackage) const = 0;
+
+ //
+ // These are available for StartElement and EndElement.
+ //
+
+ virtual const std::u16string& getElementNamespace() const = 0;
+ virtual const std::u16string& getElementName() const = 0;
+
+ //
+ // 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;
+
+ virtual const_iterator beginAttributes() const = 0;
+ virtual const_iterator endAttributes() const = 0;
+ virtual size_t getAttributeCount() const = 0;
+ const_iterator findAttribute(StringPiece16 namespaceUri, StringPiece16 name) const;
+};
+
+//
+// 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 void XmlPullParser::skipCurrentElement(XmlPullParser* parser) {
+ int depth = 1;
+ while (depth > 0) {
+ switch (parser->next()) {
+ case Event::kEndDocument:
+ case Event::kBadDocument:
+ return;
+ case Event::kStartElement:
+ depth++;
+ break;
+ case Event::kEndElement:
+ depth--;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+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);
+}
+
+inline bool XmlPullParser::Attribute::operator<(const Attribute& rhs) const {
+ return compare(rhs) < 0;
+}
+
+inline bool XmlPullParser::Attribute::operator==(const Attribute& rhs) const {
+ return compare(rhs) == 0;
+}
+
+inline bool XmlPullParser::Attribute::operator!=(const Attribute& rhs) const {
+ 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;
+}
+
+} // namespace aapt
+
+#endif // AAPT_XML_PULL_PARSER_H
diff --git a/tools/aapt2/ZipEntry.cpp b/tools/aapt2/ZipEntry.cpp
new file mode 100644
index 0000000..891b4e1
--- /dev/null
+++ b/tools/aapt2/ZipEntry.cpp
@@ -0,0 +1,745 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Access to entries in a Zip archive.
+//
+
+#define LOG_TAG "zip"
+
+#include "ZipEntry.h"
+#include <utils/Log.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+namespace aapt {
+
+using namespace android;
+
+/*
+ * Initialize a new ZipEntry structure from a FILE* positioned at a
+ * CentralDirectoryEntry.
+ *
+ * On exit, the file pointer will be at the start of the next CDE or
+ * at the EOCD.
+ */
+status_t ZipEntry::initFromCDE(FILE* fp)
+{
+ status_t result;
+ long posn;
+ bool hasDD;
+
+ //ALOGV("initFromCDE ---\n");
+
+ /* read the CDE */
+ result = mCDE.read(fp);
+ if (result != NO_ERROR) {
+ ALOGD("mCDE.read failed\n");
+ return result;
+ }
+
+ //mCDE.dump();
+
+ /* using the info in the CDE, go load up the LFH */
+ posn = ftell(fp);
+ if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) {
+ ALOGD("local header seek failed (%ld)\n",
+ mCDE.mLocalHeaderRelOffset);
+ return UNKNOWN_ERROR;
+ }
+
+ result = mLFH.read(fp);
+ if (result != NO_ERROR) {
+ ALOGD("mLFH.read failed\n");
+ return result;
+ }
+
+ if (fseek(fp, posn, SEEK_SET) != 0)
+ return UNKNOWN_ERROR;
+
+ //mLFH.dump();
+
+ /*
+ * We *might* need to read the Data Descriptor at this point and
+ * integrate it into the LFH. If this bit is set, the CRC-32,
+ * compressed size, and uncompressed size will be zero. In practice
+ * these seem to be rare.
+ */
+ hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0;
+ if (hasDD) {
+ // do something clever
+ //ALOGD("+++ has data descriptor\n");
+ }
+
+ /*
+ * Sanity-check the LFH. Note that this will fail if the "kUsesDataDescr"
+ * flag is set, because the LFH is incomplete. (Not a problem, since we
+ * prefer the CDE values.)
+ */
+ if (!hasDD && !compareHeaders()) {
+ ALOGW("warning: header mismatch\n");
+ // keep going?
+ }
+
+ /*
+ * If the mVersionToExtract is greater than 20, we may have an
+ * issue unpacking the record -- could be encrypted, compressed
+ * with something we don't support, or use Zip64 extensions. We
+ * can defer worrying about that to when we're extracting data.
+ */
+
+ return NO_ERROR;
+}
+
+/*
+ * Initialize a new entry. Pass in the file name and an optional comment.
+ *
+ * Initializes the CDE and the LFH.
+ */
+void ZipEntry::initNew(const char* fileName, const char* comment)
+{
+ assert(fileName != NULL && *fileName != '\0'); // name required
+
+ /* most fields are properly initialized by constructor */
+ mCDE.mVersionMadeBy = kDefaultMadeBy;
+ mCDE.mVersionToExtract = kDefaultVersion;
+ mCDE.mCompressionMethod = kCompressStored;
+ mCDE.mFileNameLength = strlen(fileName);
+ if (comment != NULL)
+ mCDE.mFileCommentLength = strlen(comment);
+ mCDE.mExternalAttrs = 0x81b60020; // matches what WinZip does
+
+ if (mCDE.mFileNameLength > 0) {
+ mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1];
+ strcpy((char*) mCDE.mFileName, fileName);
+ }
+ if (mCDE.mFileCommentLength > 0) {
+ /* TODO: stop assuming null-terminated ASCII here? */
+ mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1];
+ strcpy((char*) mCDE.mFileComment, comment);
+ }
+
+ copyCDEtoLFH();
+}
+
+/*
+ * Initialize a new entry, starting with the ZipEntry from a different
+ * archive.
+ *
+ * Initializes the CDE and the LFH.
+ */
+status_t ZipEntry::initFromExternal(const ZipFile* /* pZipFile */,
+ const ZipEntry* pEntry, const char* storageName)
+{
+ mCDE = pEntry->mCDE;
+ if (storageName && *storageName != 0) {
+ mCDE.mFileNameLength = strlen(storageName);
+ mCDE.mFileName = new unsigned char[mCDE.mFileNameLength + 1];
+ strcpy((char*) mCDE.mFileName, storageName);
+ }
+
+ // Check whether we got all the memory needed.
+ if ((mCDE.mFileNameLength > 0 && mCDE.mFileName == NULL) ||
+ (mCDE.mFileCommentLength > 0 && mCDE.mFileComment == NULL) ||
+ (mCDE.mExtraFieldLength > 0 && mCDE.mExtraField == NULL)) {
+ return NO_MEMORY;
+ }
+
+ /* construct the LFH from the CDE */
+ copyCDEtoLFH();
+
+ /*
+ * The LFH "extra" field is independent of the CDE "extra", so we
+ * handle it here.
+ */
+ assert(mLFH.mExtraField == NULL);
+ mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength;
+ if (mLFH.mExtraFieldLength > 0) {
+ mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1];
+ if (mLFH.mExtraField == NULL)
+ return NO_MEMORY;
+ memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField,
+ mLFH.mExtraFieldLength+1);
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Insert pad bytes in the LFH by tweaking the "extra" field. This will
+ * potentially confuse something that put "extra" data in here earlier,
+ * but I can't find an actual problem.
+ */
+status_t ZipEntry::addPadding(int padding)
+{
+ if (padding <= 0)
+ return INVALID_OPERATION;
+
+ //ALOGI("HEY: adding %d pad bytes to existing %d in %s\n",
+ // padding, mLFH.mExtraFieldLength, mCDE.mFileName);
+
+ if (mLFH.mExtraFieldLength > 0) {
+ /* extend existing field */
+ unsigned char* newExtra;
+
+ newExtra = new unsigned char[mLFH.mExtraFieldLength + padding];
+ if (newExtra == NULL)
+ return NO_MEMORY;
+ memset(newExtra + mLFH.mExtraFieldLength, 0, padding);
+ memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength);
+
+ delete[] mLFH.mExtraField;
+ mLFH.mExtraField = newExtra;
+ mLFH.mExtraFieldLength += padding;
+ } else {
+ /* create new field */
+ mLFH.mExtraField = new unsigned char[padding];
+ memset(mLFH.mExtraField, 0, padding);
+ mLFH.mExtraFieldLength = padding;
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Set the fields in the LFH equal to the corresponding fields in the CDE.
+ *
+ * This does not touch the LFH "extra" field.
+ */
+void ZipEntry::copyCDEtoLFH(void)
+{
+ mLFH.mVersionToExtract = mCDE.mVersionToExtract;
+ mLFH.mGPBitFlag = mCDE.mGPBitFlag;
+ mLFH.mCompressionMethod = mCDE.mCompressionMethod;
+ mLFH.mLastModFileTime = mCDE.mLastModFileTime;
+ mLFH.mLastModFileDate = mCDE.mLastModFileDate;
+ mLFH.mCRC32 = mCDE.mCRC32;
+ mLFH.mCompressedSize = mCDE.mCompressedSize;
+ mLFH.mUncompressedSize = mCDE.mUncompressedSize;
+ mLFH.mFileNameLength = mCDE.mFileNameLength;
+ // the "extra field" is independent
+
+ delete[] mLFH.mFileName;
+ if (mLFH.mFileNameLength > 0) {
+ mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1];
+ strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName);
+ } else {
+ mLFH.mFileName = NULL;
+ }
+}
+
+/*
+ * Set some information about a file after we add it.
+ */
+void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32,
+ int compressionMethod)
+{
+ mCDE.mCompressionMethod = compressionMethod;
+ mCDE.mCRC32 = crc32;
+ mCDE.mCompressedSize = compLen;
+ mCDE.mUncompressedSize = uncompLen;
+ mCDE.mCompressionMethod = compressionMethod;
+ if (compressionMethod == kCompressDeflated) {
+ mCDE.mGPBitFlag |= 0x0002; // indicates maximum compression used
+ }
+ copyCDEtoLFH();
+}
+
+/*
+ * See if the data in mCDE and mLFH match up. This is mostly useful for
+ * debugging these classes, but it can be used to identify damaged
+ * archives.
+ *
+ * Returns "false" if they differ.
+ */
+bool ZipEntry::compareHeaders(void) const
+{
+ if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) {
+ ALOGV("cmp: VersionToExtract\n");
+ return false;
+ }
+ if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) {
+ ALOGV("cmp: GPBitFlag\n");
+ return false;
+ }
+ if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) {
+ ALOGV("cmp: CompressionMethod\n");
+ return false;
+ }
+ if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) {
+ ALOGV("cmp: LastModFileTime\n");
+ return false;
+ }
+ if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) {
+ ALOGV("cmp: LastModFileDate\n");
+ return false;
+ }
+ if (mCDE.mCRC32 != mLFH.mCRC32) {
+ ALOGV("cmp: CRC32\n");
+ return false;
+ }
+ if (mCDE.mCompressedSize != mLFH.mCompressedSize) {
+ ALOGV("cmp: CompressedSize\n");
+ return false;
+ }
+ if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) {
+ ALOGV("cmp: UncompressedSize\n");
+ return false;
+ }
+ if (mCDE.mFileNameLength != mLFH.mFileNameLength) {
+ ALOGV("cmp: FileNameLength\n");
+ return false;
+ }
+#if 0 // this seems to be used for padding, not real data
+ if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) {
+ ALOGV("cmp: ExtraFieldLength\n");
+ return false;
+ }
+#endif
+ if (mCDE.mFileName != NULL) {
+ if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) {
+ ALOGV("cmp: FileName\n");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+/*
+ * Convert the DOS date/time stamp into a UNIX time stamp.
+ */
+time_t ZipEntry::getModWhen(void) const
+{
+ struct tm parts;
+
+ parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1;
+ parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5;
+ parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11;
+ parts.tm_mday = (mCDE.mLastModFileDate & 0x001f);
+ parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1;
+ parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80;
+ parts.tm_wday = parts.tm_yday = 0;
+ parts.tm_isdst = -1; // DST info "not available"
+
+ return mktime(&parts);
+}
+
+/*
+ * Set the CDE/LFH timestamp from UNIX time.
+ */
+void ZipEntry::setModWhen(time_t when)
+{
+#if !defined(_WIN32)
+ struct tm tmResult;
+#endif
+ time_t even;
+ unsigned short zdate, ztime;
+
+ struct tm* ptm;
+
+ /* round up to an even number of seconds */
+ even = (time_t)(((unsigned long)(when) + 1) & (~1));
+
+ /* expand */
+#if !defined(_WIN32)
+ ptm = localtime_r(&even, &tmResult);
+#else
+ ptm = localtime(&even);
+#endif
+
+ int year;
+ year = ptm->tm_year;
+ if (year < 80)
+ year = 80;
+
+ zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday;
+ ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
+
+ mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime;
+ mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate;
+}
+
+
+/*
+ * ===========================================================================
+ * ZipEntry::LocalFileHeader
+ * ===========================================================================
+ */
+
+/*
+ * Read a local file header.
+ *
+ * On entry, "fp" points to the signature at the start of the header.
+ * On exit, "fp" points to the start of data.
+ */
+status_t ZipEntry::LocalFileHeader::read(FILE* fp)
+{
+ status_t result = NO_ERROR;
+ unsigned char buf[kLFHLen];
+
+ assert(mFileName == NULL);
+ assert(mExtraField == NULL);
+
+ if (fread(buf, 1, kLFHLen, fp) != kLFHLen) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
+ ALOGD("whoops: didn't find expected signature\n");
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]);
+ mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]);
+ mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]);
+ mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]);
+ mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]);
+ mCRC32 = ZipEntry::getLongLE(&buf[0x0e]);
+ mCompressedSize = ZipEntry::getLongLE(&buf[0x12]);
+ mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]);
+ mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]);
+ mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]);
+
+ // TODO: validate sizes
+
+ /* grab filename */
+ if (mFileNameLength != 0) {
+ mFileName = new unsigned char[mFileNameLength+1];
+ if (mFileName == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mFileName[mFileNameLength] = '\0';
+ }
+
+ /* grab extra field */
+ if (mExtraFieldLength != 0) {
+ mExtraField = new unsigned char[mExtraFieldLength+1];
+ if (mExtraField == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mExtraField[mExtraFieldLength] = '\0';
+ }
+
+bail:
+ return result;
+}
+
+/*
+ * Write a local file header.
+ */
+status_t ZipEntry::LocalFileHeader::write(FILE* fp)
+{
+ unsigned char buf[kLFHLen];
+
+ ZipEntry::putLongLE(&buf[0x00], kSignature);
+ ZipEntry::putShortLE(&buf[0x04], mVersionToExtract);
+ ZipEntry::putShortLE(&buf[0x06], mGPBitFlag);
+ ZipEntry::putShortLE(&buf[0x08], mCompressionMethod);
+ ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime);
+ ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate);
+ ZipEntry::putLongLE(&buf[0x0e], mCRC32);
+ ZipEntry::putLongLE(&buf[0x12], mCompressedSize);
+ ZipEntry::putLongLE(&buf[0x16], mUncompressedSize);
+ ZipEntry::putShortLE(&buf[0x1a], mFileNameLength);
+ ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength);
+
+ if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen)
+ return UNKNOWN_ERROR;
+
+ /* write filename */
+ if (mFileNameLength != 0) {
+ if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
+ return UNKNOWN_ERROR;
+ }
+
+ /* write "extra field" */
+ if (mExtraFieldLength != 0) {
+ if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+
+/*
+ * Dump the contents of a LocalFileHeader object.
+ */
+void ZipEntry::LocalFileHeader::dump(void) const
+{
+ ALOGD(" LocalFileHeader contents:\n");
+ ALOGD(" versToExt=%u gpBits=0x%04x compression=%u\n",
+ mVersionToExtract, mGPBitFlag, mCompressionMethod);
+ ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
+ mLastModFileTime, mLastModFileDate, mCRC32);
+ ALOGD(" compressedSize=%lu uncompressedSize=%lu\n",
+ mCompressedSize, mUncompressedSize);
+ ALOGD(" filenameLen=%u extraLen=%u\n",
+ mFileNameLength, mExtraFieldLength);
+ if (mFileName != NULL)
+ ALOGD(" filename: '%s'\n", mFileName);
+}
+
+
+/*
+ * ===========================================================================
+ * ZipEntry::CentralDirEntry
+ * ===========================================================================
+ */
+
+/*
+ * Read the central dir entry that appears next in the file.
+ *
+ * On entry, "fp" should be positioned on the signature bytes for the
+ * entry. On exit, "fp" will point at the signature word for the next
+ * entry or for the EOCD.
+ */
+status_t ZipEntry::CentralDirEntry::read(FILE* fp)
+{
+ status_t result = NO_ERROR;
+ unsigned char buf[kCDELen];
+
+ /* no re-use */
+ assert(mFileName == NULL);
+ assert(mExtraField == NULL);
+ assert(mFileComment == NULL);
+
+ if (fread(buf, 1, kCDELen, fp) != kCDELen) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
+ ALOGD("Whoops: didn't find expected signature\n");
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]);
+ mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]);
+ mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]);
+ mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]);
+ mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]);
+ mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]);
+ mCRC32 = ZipEntry::getLongLE(&buf[0x10]);
+ mCompressedSize = ZipEntry::getLongLE(&buf[0x14]);
+ mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]);
+ mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]);
+ mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]);
+ mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]);
+ mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]);
+ mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]);
+ mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]);
+ mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]);
+
+ // TODO: validate sizes and offsets
+
+ /* grab filename */
+ if (mFileNameLength != 0) {
+ mFileName = new unsigned char[mFileNameLength+1];
+ if (mFileName == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mFileName[mFileNameLength] = '\0';
+ }
+
+ /* read "extra field" */
+ if (mExtraFieldLength != 0) {
+ mExtraField = new unsigned char[mExtraFieldLength+1];
+ if (mExtraField == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mExtraField[mExtraFieldLength] = '\0';
+ }
+
+
+ /* grab comment, if any */
+ if (mFileCommentLength != 0) {
+ mFileComment = new unsigned char[mFileCommentLength+1];
+ if (mFileComment == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
+ {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mFileComment[mFileCommentLength] = '\0';
+ }
+
+bail:
+ return result;
+}
+
+/*
+ * Write a central dir entry.
+ */
+status_t ZipEntry::CentralDirEntry::write(FILE* fp)
+{
+ unsigned char buf[kCDELen];
+
+ ZipEntry::putLongLE(&buf[0x00], kSignature);
+ ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy);
+ ZipEntry::putShortLE(&buf[0x06], mVersionToExtract);
+ ZipEntry::putShortLE(&buf[0x08], mGPBitFlag);
+ ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod);
+ ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime);
+ ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate);
+ ZipEntry::putLongLE(&buf[0x10], mCRC32);
+ ZipEntry::putLongLE(&buf[0x14], mCompressedSize);
+ ZipEntry::putLongLE(&buf[0x18], mUncompressedSize);
+ ZipEntry::putShortLE(&buf[0x1c], mFileNameLength);
+ ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength);
+ ZipEntry::putShortLE(&buf[0x20], mFileCommentLength);
+ ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart);
+ ZipEntry::putShortLE(&buf[0x24], mInternalAttrs);
+ ZipEntry::putLongLE(&buf[0x26], mExternalAttrs);
+ ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset);
+
+ if (fwrite(buf, 1, kCDELen, fp) != kCDELen)
+ return UNKNOWN_ERROR;
+
+ /* write filename */
+ if (mFileNameLength != 0) {
+ if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
+ return UNKNOWN_ERROR;
+ }
+
+ /* write "extra field" */
+ if (mExtraFieldLength != 0) {
+ if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
+ return UNKNOWN_ERROR;
+ }
+
+ /* write comment */
+ if (mFileCommentLength != 0) {
+ if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Dump the contents of a CentralDirEntry object.
+ */
+void ZipEntry::CentralDirEntry::dump(void) const
+{
+ ALOGD(" CentralDirEntry contents:\n");
+ ALOGD(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n",
+ mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod);
+ ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
+ mLastModFileTime, mLastModFileDate, mCRC32);
+ ALOGD(" compressedSize=%lu uncompressedSize=%lu\n",
+ mCompressedSize, mUncompressedSize);
+ ALOGD(" filenameLen=%u extraLen=%u commentLen=%u\n",
+ mFileNameLength, mExtraFieldLength, mFileCommentLength);
+ ALOGD(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n",
+ mDiskNumberStart, mInternalAttrs, mExternalAttrs,
+ mLocalHeaderRelOffset);
+
+ if (mFileName != NULL)
+ ALOGD(" filename: '%s'\n", mFileName);
+ if (mFileComment != NULL)
+ ALOGD(" comment: '%s'\n", mFileComment);
+}
+
+/*
+ * Copy-assignment operator for CentralDirEntry.
+ */
+ZipEntry::CentralDirEntry& ZipEntry::CentralDirEntry::operator=(const ZipEntry::CentralDirEntry& src) {
+ if (this == &src) {
+ return *this;
+ }
+
+ // Free up old data.
+ delete[] mFileName;
+ delete[] mExtraField;
+ delete[] mFileComment;
+
+ // Copy scalars.
+ mVersionMadeBy = src.mVersionMadeBy;
+ mVersionToExtract = src.mVersionToExtract;
+ mGPBitFlag = src.mGPBitFlag;
+ mCompressionMethod = src.mCompressionMethod;
+ mLastModFileTime = src.mLastModFileTime;
+ mLastModFileDate = src.mLastModFileDate;
+ mCRC32 = src.mCRC32;
+ mCompressedSize = src.mCompressedSize;
+ mUncompressedSize = src.mUncompressedSize;
+ mFileNameLength = src.mFileNameLength;
+ mExtraFieldLength = src.mExtraFieldLength;
+ mFileCommentLength = src.mFileCommentLength;
+ mDiskNumberStart = src.mDiskNumberStart;
+ mInternalAttrs = src.mInternalAttrs;
+ mExternalAttrs = src.mExternalAttrs;
+ mLocalHeaderRelOffset = src.mLocalHeaderRelOffset;
+
+ // Copy strings, if necessary.
+ if (mFileNameLength > 0) {
+ mFileName = new unsigned char[mFileNameLength + 1];
+ if (mFileName != NULL)
+ strcpy((char*)mFileName, (char*)src.mFileName);
+ } else {
+ mFileName = NULL;
+ }
+ if (mFileCommentLength > 0) {
+ mFileComment = new unsigned char[mFileCommentLength + 1];
+ if (mFileComment != NULL)
+ strcpy((char*)mFileComment, (char*)src.mFileComment);
+ } else {
+ mFileComment = NULL;
+ }
+ if (mExtraFieldLength > 0) {
+ /* we null-terminate this, though it may not be a string */
+ mExtraField = new unsigned char[mExtraFieldLength + 1];
+ if (mExtraField != NULL)
+ memcpy(mExtraField, src.mExtraField, mExtraFieldLength + 1);
+ } else {
+ mExtraField = NULL;
+ }
+
+ return *this;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ZipEntry.h b/tools/aapt2/ZipEntry.h
new file mode 100644
index 0000000..2745a43
--- /dev/null
+++ b/tools/aapt2/ZipEntry.h
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Zip archive entries.
+//
+// The ZipEntry class is tightly meshed with the ZipFile class.
+//
+#ifndef __LIBS_ZIPENTRY_H
+#define __LIBS_ZIPENTRY_H
+
+#include <utils/Errors.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+
+namespace aapt {
+
+using android::status_t;
+
+class ZipFile;
+
+/*
+ * ZipEntry objects represent a single entry in a Zip archive.
+ *
+ * You can use one of these to get or set information about an entry, but
+ * there are no functions here for accessing the data itself. (We could
+ * tuck a pointer to the ZipFile in here for convenience, but that raises
+ * the likelihood of using ZipEntry objects after discarding the ZipFile.)
+ *
+ * File information is stored in two places: next to the file data (the Local
+ * File Header, and possibly a Data Descriptor), and at the end of the file
+ * (the Central Directory Entry). The two must be kept in sync.
+ */
+class ZipEntry {
+public:
+ friend class ZipFile;
+
+ ZipEntry(void)
+ : mDeleted(false), mMarked(false)
+ {}
+ ~ZipEntry(void) {}
+
+ /*
+ * Returns "true" if the data is compressed.
+ */
+ bool isCompressed(void) const {
+ return mCDE.mCompressionMethod != kCompressStored;
+ }
+ int getCompressionMethod(void) const { return mCDE.mCompressionMethod; }
+
+ /*
+ * Return the uncompressed length.
+ */
+ off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; }
+
+ /*
+ * Return the compressed length. For uncompressed data, this returns
+ * the same thing as getUncompresesdLen().
+ */
+ off_t getCompressedLen(void) const { return mCDE.mCompressedSize; }
+
+ /*
+ * Return the offset of the local file header.
+ */
+ off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; }
+
+ /*
+ * Return the absolute file offset of the start of the compressed or
+ * uncompressed data.
+ */
+ off_t getFileOffset(void) const {
+ return mCDE.mLocalHeaderRelOffset +
+ LocalFileHeader::kLFHLen +
+ mLFH.mFileNameLength +
+ mLFH.mExtraFieldLength;
+ }
+
+ /*
+ * Return the data CRC.
+ */
+ unsigned long getCRC32(void) const { return mCDE.mCRC32; }
+
+ /*
+ * Return file modification time in UNIX seconds-since-epoch.
+ */
+ time_t getModWhen(void) const;
+
+ /*
+ * Return the archived file name.
+ */
+ const char* getFileName(void) const { return (const char*) mCDE.mFileName; }
+
+ /*
+ * Application-defined "mark". Can be useful when synchronizing the
+ * contents of an archive with contents on disk.
+ */
+ bool getMarked(void) const { return mMarked; }
+ void setMarked(bool val) { mMarked = val; }
+
+ /*
+ * Some basic functions for raw data manipulation. "LE" means
+ * Little Endian.
+ */
+ static inline unsigned short getShortLE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8);
+ }
+ static inline unsigned long getLongLE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+ }
+ static inline void putShortLE(unsigned char* buf, short val) {
+ buf[0] = (unsigned char) val;
+ buf[1] = (unsigned char) (val >> 8);
+ }
+ static inline void putLongLE(unsigned char* buf, long val) {
+ buf[0] = (unsigned char) val;
+ buf[1] = (unsigned char) (val >> 8);
+ buf[2] = (unsigned char) (val >> 16);
+ buf[3] = (unsigned char) (val >> 24);
+ }
+
+ /* defined for Zip archives */
+ enum {
+ kCompressStored = 0, // no compression
+ // shrunk = 1,
+ // reduced 1 = 2,
+ // reduced 2 = 3,
+ // reduced 3 = 4,
+ // reduced 4 = 5,
+ // imploded = 6,
+ // tokenized = 7,
+ kCompressDeflated = 8, // standard deflate
+ // Deflate64 = 9,
+ // lib imploded = 10,
+ // reserved = 11,
+ // bzip2 = 12,
+ };
+
+ /*
+ * Deletion flag. If set, the entry will be removed on the next
+ * call to "flush".
+ */
+ bool getDeleted(void) const { return mDeleted; }
+
+protected:
+ /*
+ * Initialize the structure from the file, which is pointing at
+ * our Central Directory entry.
+ */
+ status_t initFromCDE(FILE* fp);
+
+ /*
+ * Initialize the structure for a new file. We need the filename
+ * and comment so that we can properly size the LFH area. The
+ * filename is mandatory, the comment is optional.
+ */
+ void initNew(const char* fileName, const char* comment);
+
+ /*
+ * Initialize the structure with the contents of a ZipEntry from
+ * another file. If fileName is non-NULL, override the name with fileName.
+ */
+ status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry,
+ const char* fileName);
+
+ /*
+ * Add some pad bytes to the LFH. We do this by adding or resizing
+ * the "extra" field.
+ */
+ status_t addPadding(int padding);
+
+ /*
+ * Set information about the data for this entry.
+ */
+ void setDataInfo(long uncompLen, long compLen, unsigned long crc32,
+ int compressionMethod);
+
+ /*
+ * Set the modification date.
+ */
+ void setModWhen(time_t when);
+
+ /*
+ * Set the offset of the local file header, relative to the start of
+ * the current file.
+ */
+ void setLFHOffset(off_t offset) {
+ mCDE.mLocalHeaderRelOffset = (long) offset;
+ }
+
+ /* mark for deletion; used by ZipFile::remove() */
+ void setDeleted(void) { mDeleted = true; }
+
+private:
+ /* these are private and not defined */
+ ZipEntry(const ZipEntry& src);
+ ZipEntry& operator=(const ZipEntry& src);
+
+ /* returns "true" if the CDE and the LFH agree */
+ bool compareHeaders(void) const;
+ void copyCDEtoLFH(void);
+
+ bool mDeleted; // set if entry is pending deletion
+ bool mMarked; // app-defined marker
+
+ /*
+ * Every entry in the Zip archive starts off with one of these.
+ */
+ class LocalFileHeader {
+ public:
+ LocalFileHeader(void) :
+ mVersionToExtract(0),
+ mGPBitFlag(0),
+ mCompressionMethod(0),
+ mLastModFileTime(0),
+ mLastModFileDate(0),
+ mCRC32(0),
+ mCompressedSize(0),
+ mUncompressedSize(0),
+ mFileNameLength(0),
+ mExtraFieldLength(0),
+ mFileName(NULL),
+ mExtraField(NULL)
+ {}
+ virtual ~LocalFileHeader(void) {
+ delete[] mFileName;
+ delete[] mExtraField;
+ }
+
+ status_t read(FILE* fp);
+ status_t write(FILE* fp);
+
+ // unsigned long mSignature;
+ unsigned short mVersionToExtract;
+ unsigned short mGPBitFlag;
+ unsigned short mCompressionMethod;
+ unsigned short mLastModFileTime;
+ unsigned short mLastModFileDate;
+ unsigned long mCRC32;
+ unsigned long mCompressedSize;
+ unsigned long mUncompressedSize;
+ unsigned short mFileNameLength;
+ unsigned short mExtraFieldLength;
+ unsigned char* mFileName;
+ unsigned char* mExtraField;
+
+ enum {
+ kSignature = 0x04034b50,
+ kLFHLen = 30, // LocalFileHdr len, excl. var fields
+ };
+
+ void dump(void) const;
+ };
+
+ /*
+ * Every entry in the Zip archive has one of these in the "central
+ * directory" at the end of the file.
+ */
+ class CentralDirEntry {
+ public:
+ CentralDirEntry(void) :
+ mVersionMadeBy(0),
+ mVersionToExtract(0),
+ mGPBitFlag(0),
+ mCompressionMethod(0),
+ mLastModFileTime(0),
+ mLastModFileDate(0),
+ mCRC32(0),
+ mCompressedSize(0),
+ mUncompressedSize(0),
+ mFileNameLength(0),
+ mExtraFieldLength(0),
+ mFileCommentLength(0),
+ mDiskNumberStart(0),
+ mInternalAttrs(0),
+ mExternalAttrs(0),
+ mLocalHeaderRelOffset(0),
+ mFileName(NULL),
+ mExtraField(NULL),
+ mFileComment(NULL)
+ {}
+ virtual ~CentralDirEntry(void) {
+ delete[] mFileName;
+ delete[] mExtraField;
+ delete[] mFileComment;
+ }
+
+ status_t read(FILE* fp);
+ status_t write(FILE* fp);
+
+ CentralDirEntry& operator=(const CentralDirEntry& src);
+
+ // unsigned long mSignature;
+ unsigned short mVersionMadeBy;
+ unsigned short mVersionToExtract;
+ unsigned short mGPBitFlag;
+ unsigned short mCompressionMethod;
+ unsigned short mLastModFileTime;
+ unsigned short mLastModFileDate;
+ unsigned long mCRC32;
+ unsigned long mCompressedSize;
+ unsigned long mUncompressedSize;
+ unsigned short mFileNameLength;
+ unsigned short mExtraFieldLength;
+ unsigned short mFileCommentLength;
+ unsigned short mDiskNumberStart;
+ unsigned short mInternalAttrs;
+ unsigned long mExternalAttrs;
+ unsigned long mLocalHeaderRelOffset;
+ unsigned char* mFileName;
+ unsigned char* mExtraField;
+ unsigned char* mFileComment;
+
+ void dump(void) const;
+
+ enum {
+ kSignature = 0x02014b50,
+ kCDELen = 46, // CentralDirEnt len, excl. var fields
+ };
+ };
+
+ enum {
+ //kDataDescriptorSignature = 0x08074b50, // currently unused
+ kDataDescriptorLen = 16, // four 32-bit fields
+
+ kDefaultVersion = 20, // need deflate, nothing much else
+ kDefaultMadeBy = 0x0317, // 03=UNIX, 17=spec v2.3
+ kUsesDataDescr = 0x0008, // GPBitFlag bit 3
+ };
+
+ LocalFileHeader mLFH;
+ CentralDirEntry mCDE;
+};
+
+}; // namespace aapt
+
+#endif // __LIBS_ZIPENTRY_H
diff --git a/tools/aapt2/ZipFile.cpp b/tools/aapt2/ZipFile.cpp
new file mode 100644
index 0000000..268c15e
--- /dev/null
+++ b/tools/aapt2/ZipFile.cpp
@@ -0,0 +1,1306 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Access to Zip archives.
+//
+
+#define LOG_TAG "zip"
+
+#include <androidfw/ZipUtils.h>
+#include <utils/Log.h>
+
+#include "ZipFile.h"
+#include "Util.h"
+
+#include <zlib.h>
+#define DEF_MEM_LEVEL 8 // normally in zutil.h?
+
+#include <memory.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <assert.h>
+
+namespace aapt {
+
+using namespace android;
+
+/*
+ * Some environments require the "b", some choke on it.
+ */
+#define FILE_OPEN_RO "rb"
+#define FILE_OPEN_RW "r+b"
+#define FILE_OPEN_RW_CREATE "w+b"
+
+/* should live somewhere else? */
+static status_t errnoToStatus(int err)
+{
+ if (err == ENOENT)
+ return NAME_NOT_FOUND;
+ else if (err == EACCES)
+ return PERMISSION_DENIED;
+ else
+ return UNKNOWN_ERROR;
+}
+
+/*
+ * Open a file and parse its guts.
+ */
+status_t ZipFile::open(const char* zipFileName, int flags)
+{
+ bool newArchive = false;
+
+ assert(mZipFp == NULL); // no reopen
+
+ if ((flags & kOpenTruncate))
+ flags |= kOpenCreate; // trunc implies create
+
+ if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite))
+ return INVALID_OPERATION; // not both
+ if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite)))
+ return INVALID_OPERATION; // not neither
+ if ((flags & kOpenCreate) && !(flags & kOpenReadWrite))
+ return INVALID_OPERATION; // create requires write
+
+ if (flags & kOpenTruncate) {
+ newArchive = true;
+ } else {
+ newArchive = (access(zipFileName, F_OK) != 0);
+ if (!(flags & kOpenCreate) && newArchive) {
+ /* not creating, must already exist */
+ ALOGD("File %s does not exist", zipFileName);
+ return NAME_NOT_FOUND;
+ }
+ }
+
+ /* open the file */
+ const char* openflags;
+ if (flags & kOpenReadWrite) {
+ if (newArchive)
+ openflags = FILE_OPEN_RW_CREATE;
+ else
+ openflags = FILE_OPEN_RW;
+ } else {
+ openflags = FILE_OPEN_RO;
+ }
+ mZipFp = fopen(zipFileName, openflags);
+ if (mZipFp == NULL) {
+ int err = errno;
+ ALOGD("fopen failed: %d\n", err);
+ return errnoToStatus(err);
+ }
+
+ status_t result;
+ if (!newArchive) {
+ /*
+ * Load the central directory. If that fails, then this probably
+ * isn't a Zip archive.
+ */
+ result = readCentralDir();
+ } else {
+ /*
+ * Newly-created. The EndOfCentralDir constructor actually
+ * sets everything to be the way we want it (all zeroes). We
+ * set mNeedCDRewrite so that we create *something* if the
+ * caller doesn't add any files. (We could also just unlink
+ * the file if it's brand new and nothing was added, but that's
+ * probably doing more than we really should -- the user might
+ * have a need for empty zip files.)
+ */
+ mNeedCDRewrite = true;
+ result = NO_ERROR;
+ }
+
+ if (flags & kOpenReadOnly)
+ mReadOnly = true;
+ else
+ assert(!mReadOnly);
+
+ return result;
+}
+
+/*
+ * Return the Nth entry in the archive.
+ */
+ZipEntry* ZipFile::getEntryByIndex(int idx) const
+{
+ if (idx < 0 || idx >= (int) mEntries.size())
+ return NULL;
+
+ return mEntries[idx];
+}
+
+/*
+ * Find an entry by name.
+ */
+ZipEntry* ZipFile::getEntryByName(const char* fileName) const
+{
+ /*
+ * Do a stupid linear string-compare search.
+ *
+ * There are various ways to speed this up, especially since it's rare
+ * to intermingle changes to the archive with "get by name" calls. We
+ * don't want to sort the mEntries vector itself, however, because
+ * it's used to recreate the Central Directory.
+ *
+ * (Hash table works, parallel list of pointers in sorted order is good.)
+ */
+ int idx;
+
+ for (idx = mEntries.size()-1; idx >= 0; idx--) {
+ ZipEntry* pEntry = mEntries[idx];
+ if (!pEntry->getDeleted() &&
+ strcmp(fileName, pEntry->getFileName()) == 0)
+ {
+ return pEntry;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Empty the mEntries vector.
+ */
+void ZipFile::discardEntries(void)
+{
+ int count = mEntries.size();
+
+ while (--count >= 0)
+ delete mEntries[count];
+
+ mEntries.clear();
+}
+
+
+/*
+ * Find the central directory and read the contents.
+ *
+ * The fun thing about ZIP archives is that they may or may not be
+ * readable from start to end. In some cases, notably for archives
+ * that were written to stdout, the only length information is in the
+ * central directory at the end of the file.
+ *
+ * Of course, the central directory can be followed by a variable-length
+ * comment field, so we have to scan through it backwards. The comment
+ * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff
+ * itself, plus apparently sometimes people throw random junk on the end
+ * just for the fun of it.
+ *
+ * This is all a little wobbly. If the wrong value ends up in the EOCD
+ * area, we're hosed. This appears to be the way that everbody handles
+ * it though, so we're in pretty good company if this fails.
+ */
+status_t ZipFile::readCentralDir(void)
+{
+ status_t result = NO_ERROR;
+ unsigned char* buf = NULL;
+ off_t fileLength, seekStart;
+ long readAmount;
+ int i;
+
+ fseek(mZipFp, 0, SEEK_END);
+ fileLength = ftell(mZipFp);
+ rewind(mZipFp);
+
+ /* too small to be a ZIP archive? */
+ if (fileLength < EndOfCentralDir::kEOCDLen) {
+ ALOGD("Length is %ld -- too small\n", (long)fileLength);
+ result = INVALID_OPERATION;
+ goto bail;
+ }
+
+ buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch];
+ if (buf == NULL) {
+ ALOGD("Failure allocating %d bytes for EOCD search",
+ EndOfCentralDir::kMaxEOCDSearch);
+ result = NO_MEMORY;
+ goto bail;
+ }
+
+ if (fileLength > EndOfCentralDir::kMaxEOCDSearch) {
+ seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch;
+ readAmount = EndOfCentralDir::kMaxEOCDSearch;
+ } else {
+ seekStart = 0;
+ readAmount = (long) fileLength;
+ }
+ if (fseek(mZipFp, seekStart, SEEK_SET) != 0) {
+ ALOGD("Failure seeking to end of zip at %ld", (long) seekStart);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /* read the last part of the file into the buffer */
+ if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) {
+ ALOGD("short file? wanted %ld\n", readAmount);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /* find the end-of-central-dir magic */
+ for (i = readAmount - 4; i >= 0; i--) {
+ if (buf[i] == 0x50 &&
+ ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature)
+ {
+ ALOGV("+++ Found EOCD at buf+%d\n", i);
+ break;
+ }
+ }
+ if (i < 0) {
+ ALOGD("EOCD not found, not Zip\n");
+ result = INVALID_OPERATION;
+ goto bail;
+ }
+
+ /* extract eocd values */
+ result = mEOCD.readBuf(buf + i, readAmount - i);
+ if (result != NO_ERROR) {
+ ALOGD("Failure reading %ld bytes of EOCD values", readAmount - i);
+ goto bail;
+ }
+ //mEOCD.dump();
+
+ if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 ||
+ mEOCD.mNumEntries != mEOCD.mTotalNumEntries)
+ {
+ ALOGD("Archive spanning not supported\n");
+ result = INVALID_OPERATION;
+ goto bail;
+ }
+
+ /*
+ * So far so good. "mCentralDirSize" is the size in bytes of the
+ * central directory, so we can just seek back that far to find it.
+ * We can also seek forward mCentralDirOffset bytes from the
+ * start of the file.
+ *
+ * We're not guaranteed to have the rest of the central dir in the
+ * buffer, nor are we guaranteed that the central dir will have any
+ * sort of convenient size. We need to skip to the start of it and
+ * read the header, then the other goodies.
+ *
+ * The only thing we really need right now is the file comment, which
+ * we're hoping to preserve.
+ */
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
+ ALOGD("Failure seeking to central dir offset %ld\n",
+ mEOCD.mCentralDirOffset);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /*
+ * Loop through and read the central dir entries.
+ */
+ ALOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries);
+ int entry;
+ for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) {
+ ZipEntry* pEntry = new ZipEntry;
+
+ result = pEntry->initFromCDE(mZipFp);
+ if (result != NO_ERROR) {
+ ALOGD("initFromCDE failed\n");
+ delete pEntry;
+ goto bail;
+ }
+
+ mEntries.push_back(pEntry);
+ }
+
+
+ /*
+ * If all went well, we should now be back at the EOCD.
+ */
+ {
+ unsigned char checkBuf[4];
+ if (fread(checkBuf, 1, 4, mZipFp) != 4) {
+ ALOGD("EOCD check read failed\n");
+ result = INVALID_OPERATION;
+ goto bail;
+ }
+ if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) {
+ ALOGD("EOCD read check failed\n");
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ ALOGV("+++ EOCD read check passed\n");
+ }
+
+bail:
+ delete[] buf;
+ return result;
+}
+
+status_t ZipFile::add(const BigBuffer& buffer, const char* storageName, int compressionMethod,
+ ZipEntry** ppEntry) {
+ std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+ return add(data.get(), buffer.size(), storageName, compressionMethod, ppEntry);
+}
+
+
+/*
+ * Add a new file to the archive.
+ *
+ * This requires creating and populating a ZipEntry structure, and copying
+ * the data into the file at the appropriate position. The "appropriate
+ * position" is the current location of the central directory, which we
+ * casually overwrite (we can put it back later).
+ *
+ * If we were concerned about safety, we would want to make all changes
+ * in a temp file and then overwrite the original after everything was
+ * safely written. Not really a concern for us.
+ */
+status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size,
+ const char* storageName, int sourceType, int compressionMethod,
+ ZipEntry** ppEntry)
+{
+ ZipEntry* pEntry = NULL;
+ status_t result = NO_ERROR;
+ long lfhPosn, startPosn, endPosn, uncompressedLen;
+ FILE* inputFp = NULL;
+ unsigned long crc;
+ time_t modWhen;
+
+ if (mReadOnly)
+ return INVALID_OPERATION;
+
+ assert(compressionMethod == ZipEntry::kCompressDeflated ||
+ compressionMethod == ZipEntry::kCompressStored);
+
+ /* make sure we're in a reasonable state */
+ assert(mZipFp != NULL);
+ assert(mEntries.size() == mEOCD.mTotalNumEntries);
+
+ /* make sure it doesn't already exist */
+ if (getEntryByName(storageName) != NULL)
+ return ALREADY_EXISTS;
+
+ if (!data) {
+ inputFp = fopen(fileName, FILE_OPEN_RO);
+ if (inputFp == NULL)
+ return errnoToStatus(errno);
+ }
+
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ pEntry = new ZipEntry;
+ pEntry->initNew(storageName, NULL);
+
+ /*
+ * From here on out, failures are more interesting.
+ */
+ mNeedCDRewrite = true;
+
+ /*
+ * Write the LFH, even though it's still mostly blank. We need it
+ * as a place-holder. In theory the LFH isn't necessary, but in
+ * practice some utilities demand it.
+ */
+ lfhPosn = ftell(mZipFp);
+ pEntry->mLFH.write(mZipFp);
+ startPosn = ftell(mZipFp);
+
+ /*
+ * Copy the data in, possibly compressing it as we go.
+ */
+ if (sourceType == ZipEntry::kCompressStored) {
+ if (compressionMethod == ZipEntry::kCompressDeflated) {
+ bool failed = false;
+ result = compressFpToFp(mZipFp, inputFp, data, size, &crc);
+ if (result != NO_ERROR) {
+ ALOGD("compression failed, storing\n");
+ failed = true;
+ } else {
+ /*
+ * Make sure it has compressed "enough". This probably ought
+ * to be set through an API call, but I don't expect our
+ * criteria to change over time.
+ */
+ long src = inputFp ? ftell(inputFp) : size;
+ long dst = ftell(mZipFp) - startPosn;
+ if (dst + (dst / 10) > src) {
+ ALOGD("insufficient compression (src=%ld dst=%ld), storing\n",
+ src, dst);
+ failed = true;
+ }
+ }
+
+ if (failed) {
+ compressionMethod = ZipEntry::kCompressStored;
+ if (inputFp) rewind(inputFp);
+ fseek(mZipFp, startPosn, SEEK_SET);
+ /* fall through to kCompressStored case */
+ }
+ }
+ /* handle "no compression" request, or failed compression from above */
+ if (compressionMethod == ZipEntry::kCompressStored) {
+ if (inputFp) {
+ result = copyFpToFp(mZipFp, inputFp, &crc);
+ } else {
+ result = copyDataToFp(mZipFp, data, size, &crc);
+ }
+ if (result != NO_ERROR) {
+ // don't need to truncate; happens in CDE rewrite
+ ALOGD("failed copying data in\n");
+ goto bail;
+ }
+ }
+
+ // currently seeked to end of file
+ uncompressedLen = inputFp ? ftell(inputFp) : size;
+ } else if (sourceType == ZipEntry::kCompressDeflated) {
+ /* we should support uncompressed-from-compressed, but it's not
+ * important right now */
+ assert(compressionMethod == ZipEntry::kCompressDeflated);
+
+ bool scanResult;
+ int method;
+ long compressedLen;
+
+ scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen,
+ &compressedLen, &crc);
+ if (!scanResult || method != ZipEntry::kCompressDeflated) {
+ ALOGD("this isn't a deflated gzip file?");
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL);
+ if (result != NO_ERROR) {
+ ALOGD("failed copying gzip data in\n");
+ goto bail;
+ }
+ } else {
+ assert(false);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /*
+ * We could write the "Data Descriptor", but there doesn't seem to
+ * be any point since we're going to go back and write the LFH.
+ *
+ * Update file offsets.
+ */
+ endPosn = ftell(mZipFp); // seeked to end of compressed data
+
+ /*
+ * Success! Fill out new values.
+ */
+ pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc,
+ compressionMethod);
+ modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp));
+ pEntry->setModWhen(modWhen);
+ pEntry->setLFHOffset(lfhPosn);
+ mEOCD.mNumEntries++;
+ mEOCD.mTotalNumEntries++;
+ mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
+ mEOCD.mCentralDirOffset = endPosn;
+
+ /*
+ * Go back and write the LFH.
+ */
+ if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ pEntry->mLFH.write(mZipFp);
+
+ /*
+ * Add pEntry to the list.
+ */
+ mEntries.push_back(pEntry);
+ if (ppEntry != NULL)
+ *ppEntry = pEntry;
+ pEntry = NULL;
+
+bail:
+ if (inputFp != NULL)
+ fclose(inputFp);
+ delete pEntry;
+ return result;
+}
+
+/*
+ * Add an entry by copying it from another zip file. If "padding" is
+ * nonzero, the specified number of bytes will be added to the "extra"
+ * field in the header.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
+ const char* storageName, int padding, ZipEntry** ppEntry)
+{
+ ZipEntry* pEntry = NULL;
+ status_t result;
+ long lfhPosn, endPosn;
+
+ if (mReadOnly)
+ return INVALID_OPERATION;
+
+ /* make sure we're in a reasonable state */
+ assert(mZipFp != NULL);
+ assert(mEntries.size() == mEOCD.mTotalNumEntries);
+
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ pEntry = new ZipEntry;
+ if (pEntry == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+
+ result = pEntry->initFromExternal(pSourceZip, pSourceEntry, storageName);
+ if (result != NO_ERROR) {
+ goto bail;
+ }
+ if (padding != 0) {
+ result = pEntry->addPadding(padding);
+ if (result != NO_ERROR)
+ goto bail;
+ }
+
+ /*
+ * From here on out, failures are more interesting.
+ */
+ mNeedCDRewrite = true;
+
+ /*
+ * Write the LFH. Since we're not recompressing the data, we already
+ * have all of the fields filled out.
+ */
+ lfhPosn = ftell(mZipFp);
+ pEntry->mLFH.write(mZipFp);
+
+ /*
+ * Copy the data over.
+ *
+ * If the "has data descriptor" flag is set, we want to copy the DD
+ * fields as well. This is a fixed-size area immediately following
+ * the data.
+ */
+ if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0)
+ {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ off_t copyLen;
+ copyLen = pSourceEntry->getCompressedLen();
+ if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0)
+ copyLen += ZipEntry::kDataDescriptorLen;
+
+ if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL)
+ != NO_ERROR)
+ {
+ ALOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /*
+ * Update file offsets.
+ */
+ endPosn = ftell(mZipFp);
+
+ /*
+ * Success! Fill out new values.
+ */
+ pEntry->setLFHOffset(lfhPosn); // sets mCDE.mLocalHeaderRelOffset
+ mEOCD.mNumEntries++;
+ mEOCD.mTotalNumEntries++;
+ mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
+ mEOCD.mCentralDirOffset = endPosn;
+
+ /*
+ * Add pEntry to the list.
+ */
+ mEntries.push_back(pEntry);
+ if (ppEntry != NULL)
+ *ppEntry = pEntry;
+ pEntry = NULL;
+
+ result = NO_ERROR;
+
+bail:
+ delete pEntry;
+ return result;
+}
+
+/*
+ * Copy all of the bytes in "src" to "dst".
+ *
+ * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
+ * will be seeked immediately past the data.
+ */
+status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32)
+{
+ unsigned char tmpBuf[32768];
+ size_t count;
+
+ *pCRC32 = crc32(0L, Z_NULL, 0);
+
+ while (1) {
+ count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp);
+ if (ferror(srcFp) || ferror(dstFp))
+ return errnoToStatus(errno);
+ if (count == 0)
+ break;
+
+ *pCRC32 = crc32(*pCRC32, tmpBuf, count);
+
+ if (fwrite(tmpBuf, 1, count, dstFp) != count) {
+ ALOGD("fwrite %d bytes failed\n", (int) count);
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Copy all of the bytes in "src" to "dst".
+ *
+ * On exit, "dstFp" will be seeked immediately past the data.
+ */
+status_t ZipFile::copyDataToFp(FILE* dstFp,
+ const void* data, size_t size, unsigned long* pCRC32)
+{
+ *pCRC32 = crc32(0L, Z_NULL, 0);
+ if (size > 0) {
+ *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size);
+ if (fwrite(data, 1, size, dstFp) != size) {
+ ALOGD("fwrite %d bytes failed\n", (int) size);
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Copy some of the bytes in "src" to "dst".
+ *
+ * If "pCRC32" is NULL, the CRC will not be computed.
+ *
+ * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
+ * will be seeked immediately past the data just written.
+ */
+status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
+ unsigned long* pCRC32)
+{
+ unsigned char tmpBuf[32768];
+ size_t count;
+
+ if (pCRC32 != NULL)
+ *pCRC32 = crc32(0L, Z_NULL, 0);
+
+ while (length) {
+ long readSize;
+
+ readSize = sizeof(tmpBuf);
+ if (readSize > length)
+ readSize = length;
+
+ count = fread(tmpBuf, 1, readSize, srcFp);
+ if ((long) count != readSize) { // error or unexpected EOF
+ ALOGD("fread %d bytes failed\n", (int) readSize);
+ return UNKNOWN_ERROR;
+ }
+
+ if (pCRC32 != NULL)
+ *pCRC32 = crc32(*pCRC32, tmpBuf, count);
+
+ if (fwrite(tmpBuf, 1, count, dstFp) != count) {
+ ALOGD("fwrite %d bytes failed\n", (int) count);
+ return UNKNOWN_ERROR;
+ }
+
+ length -= readSize;
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Compress all of the data in "srcFp" and write it to "dstFp".
+ *
+ * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
+ * will be seeked immediately past the compressed data.
+ */
+status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp,
+ const void* data, size_t size, unsigned long* pCRC32)
+{
+ status_t result = NO_ERROR;
+ const size_t kBufSize = 32768;
+ unsigned char* inBuf = NULL;
+ unsigned char* outBuf = NULL;
+ z_stream zstream;
+ bool atEof = false; // no feof() aviailable yet
+ unsigned long crc;
+ int zerr;
+
+ /*
+ * Create an input buffer and an output buffer.
+ */
+ inBuf = new unsigned char[kBufSize];
+ outBuf = new unsigned char[kBufSize];
+ if (inBuf == NULL || outBuf == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+
+ /*
+ * Initialize the zlib stream.
+ */
+ memset(&zstream, 0, sizeof(zstream));
+ zstream.zalloc = Z_NULL;
+ zstream.zfree = Z_NULL;
+ zstream.opaque = Z_NULL;
+ zstream.next_in = NULL;
+ zstream.avail_in = 0;
+ zstream.next_out = outBuf;
+ zstream.avail_out = kBufSize;
+ zstream.data_type = Z_UNKNOWN;
+
+ zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION,
+ Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
+ if (zerr != Z_OK) {
+ result = UNKNOWN_ERROR;
+ if (zerr == Z_VERSION_ERROR) {
+ ALOGE("Installed zlib is not compatible with linked version (%s)\n",
+ ZLIB_VERSION);
+ } else {
+ ALOGD("Call to deflateInit2 failed (zerr=%d)\n", zerr);
+ }
+ goto bail;
+ }
+
+ crc = crc32(0L, Z_NULL, 0);
+
+ /*
+ * Loop while we have data.
+ */
+ do {
+ size_t getSize;
+ int flush;
+
+ /* only read if the input buffer is empty */
+ if (zstream.avail_in == 0 && !atEof) {
+ ALOGV("+++ reading %d bytes\n", (int)kBufSize);
+ if (data) {
+ getSize = size > kBufSize ? kBufSize : size;
+ memcpy(inBuf, data, getSize);
+ data = ((const char*)data) + getSize;
+ size -= getSize;
+ } else {
+ getSize = fread(inBuf, 1, kBufSize, srcFp);
+ if (ferror(srcFp)) {
+ ALOGD("deflate read failed (errno=%d)\n", errno);
+ goto z_bail;
+ }
+ }
+ if (getSize < kBufSize) {
+ ALOGV("+++ got %d bytes, EOF reached\n",
+ (int)getSize);
+ atEof = true;
+ }
+
+ crc = crc32(crc, inBuf, getSize);
+
+ zstream.next_in = inBuf;
+ zstream.avail_in = getSize;
+ }
+
+ if (atEof)
+ flush = Z_FINISH; /* tell zlib that we're done */
+ else
+ flush = Z_NO_FLUSH; /* more to come! */
+
+ zerr = deflate(&zstream, flush);
+ if (zerr != Z_OK && zerr != Z_STREAM_END) {
+ ALOGD("zlib deflate call failed (zerr=%d)\n", zerr);
+ result = UNKNOWN_ERROR;
+ goto z_bail;
+ }
+
+ /* write when we're full or when we're done */
+ if (zstream.avail_out == 0 ||
+ (zerr == Z_STREAM_END && zstream.avail_out != (uInt) kBufSize))
+ {
+ ALOGV("+++ writing %d bytes\n", (int) (zstream.next_out - outBuf));
+ if (fwrite(outBuf, 1, zstream.next_out - outBuf, dstFp) !=
+ (size_t)(zstream.next_out - outBuf))
+ {
+ ALOGD("write %d failed in deflate\n",
+ (int) (zstream.next_out - outBuf));
+ goto z_bail;
+ }
+
+ zstream.next_out = outBuf;
+ zstream.avail_out = kBufSize;
+ }
+ } while (zerr == Z_OK);
+
+ assert(zerr == Z_STREAM_END); /* other errors should've been caught */
+
+ *pCRC32 = crc;
+
+z_bail:
+ deflateEnd(&zstream); /* free up any allocated structures */
+
+bail:
+ delete[] inBuf;
+ delete[] outBuf;
+
+ return result;
+}
+
+/*
+ * Mark an entry as deleted.
+ *
+ * We will eventually need to crunch the file down, but if several files
+ * are being removed (perhaps as part of an "update" process) we can make
+ * things considerably faster by deferring the removal to "flush" time.
+ */
+status_t ZipFile::remove(ZipEntry* pEntry)
+{
+ /*
+ * Should verify that pEntry is actually part of this archive, and
+ * not some stray ZipEntry from a different file.
+ */
+
+ /* mark entry as deleted, and mark archive as dirty */
+ pEntry->setDeleted();
+ mNeedCDRewrite = true;
+ return NO_ERROR;
+}
+
+/*
+ * Flush any pending writes.
+ *
+ * In particular, this will crunch out deleted entries, and write the
+ * Central Directory and EOCD if we have stomped on them.
+ */
+status_t ZipFile::flush(void)
+{
+ status_t result = NO_ERROR;
+ long eocdPosn;
+ int i, count;
+
+ if (mReadOnly)
+ return INVALID_OPERATION;
+ if (!mNeedCDRewrite)
+ return NO_ERROR;
+
+ assert(mZipFp != NULL);
+
+ result = crunchArchive();
+ if (result != NO_ERROR)
+ return result;
+
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0)
+ return UNKNOWN_ERROR;
+
+ count = mEntries.size();
+ for (i = 0; i < count; i++) {
+ ZipEntry* pEntry = mEntries[i];
+ pEntry->mCDE.write(mZipFp);
+ }
+
+ eocdPosn = ftell(mZipFp);
+ mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset;
+
+ mEOCD.write(mZipFp);
+
+ /*
+ * If we had some stuff bloat up during compression and get replaced
+ * with plain files, or if we deleted some entries, there's a lot
+ * of wasted space at the end of the file. Remove it now.
+ */
+ if (ftruncate(fileno(mZipFp), ftell(mZipFp)) != 0) {
+ ALOGW("ftruncate failed %ld: %s\n", ftell(mZipFp), strerror(errno));
+ // not fatal
+ }
+
+ /* should we clear the "newly added" flag in all entries now? */
+
+ mNeedCDRewrite = false;
+ return NO_ERROR;
+}
+
+/*
+ * Crunch deleted files out of an archive by shifting the later files down.
+ *
+ * Because we're not using a temp file, we do the operation inside the
+ * current file.
+ */
+status_t ZipFile::crunchArchive(void)
+{
+ status_t result = NO_ERROR;
+ int i, count;
+ long delCount, adjust;
+
+#if 0
+ printf("CONTENTS:\n");
+ for (i = 0; i < (int) mEntries.size(); i++) {
+ printf(" %d: lfhOff=%ld del=%d\n",
+ i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted());
+ }
+ printf(" END is %ld\n", (long) mEOCD.mCentralDirOffset);
+#endif
+
+ /*
+ * Roll through the set of files, shifting them as appropriate. We
+ * could probably get a slight performance improvement by sliding
+ * multiple files down at once (because we could use larger reads
+ * when operating on batches of small files), but it's not that useful.
+ */
+ count = mEntries.size();
+ delCount = adjust = 0;
+ for (i = 0; i < count; i++) {
+ ZipEntry* pEntry = mEntries[i];
+ long span;
+
+ if (pEntry->getLFHOffset() != 0) {
+ long nextOffset;
+
+ /* Get the length of this entry by finding the offset
+ * of the next entry. Directory entries don't have
+ * file offsets, so we need to find the next non-directory
+ * entry.
+ */
+ nextOffset = 0;
+ for (int ii = i+1; nextOffset == 0 && ii < count; ii++)
+ nextOffset = mEntries[ii]->getLFHOffset();
+ if (nextOffset == 0)
+ nextOffset = mEOCD.mCentralDirOffset;
+ span = nextOffset - pEntry->getLFHOffset();
+
+ assert(span >= ZipEntry::LocalFileHeader::kLFHLen);
+ } else {
+ /* This is a directory entry. It doesn't have
+ * any actual file contents, so there's no need to
+ * move anything.
+ */
+ span = 0;
+ }
+
+ //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n",
+ // i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count);
+
+ if (pEntry->getDeleted()) {
+ adjust += span;
+ delCount++;
+
+ delete pEntry;
+ mEntries.erase(mEntries.begin() + i);
+
+ /* adjust loop control */
+ count--;
+ i--;
+ } else if (span != 0 && adjust > 0) {
+ /* shuffle this entry back */
+ //printf("+++ Shuffling '%s' back %ld\n",
+ // pEntry->getFileName(), adjust);
+ result = filemove(mZipFp, pEntry->getLFHOffset() - adjust,
+ pEntry->getLFHOffset(), span);
+ if (result != NO_ERROR) {
+ /* this is why you use a temp file */
+ ALOGE("error during crunch - archive is toast\n");
+ return result;
+ }
+
+ pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust);
+ }
+ }
+
+ /*
+ * Fix EOCD info. We have to wait until the end to do some of this
+ * because we use mCentralDirOffset to determine "span" for the
+ * last entry.
+ */
+ mEOCD.mCentralDirOffset -= adjust;
+ mEOCD.mNumEntries -= delCount;
+ mEOCD.mTotalNumEntries -= delCount;
+ mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
+
+ assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries);
+ assert(mEOCD.mNumEntries == count);
+
+ return result;
+}
+
+/*
+ * Works like memmove(), but on pieces of a file.
+ */
+status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n)
+{
+ if (dst == src || n <= 0)
+ return NO_ERROR;
+
+ unsigned char readBuf[32768];
+
+ if (dst < src) {
+ /* shift stuff toward start of file; must read from start */
+ while (n != 0) {
+ size_t getSize = sizeof(readBuf);
+ if (getSize > n)
+ getSize = n;
+
+ if (fseek(fp, (long) src, SEEK_SET) != 0) {
+ ALOGD("filemove src seek %ld failed\n", (long) src);
+ return UNKNOWN_ERROR;
+ }
+
+ if (fread(readBuf, 1, getSize, fp) != getSize) {
+ ALOGD("filemove read %ld off=%ld failed\n",
+ (long) getSize, (long) src);
+ return UNKNOWN_ERROR;
+ }
+
+ if (fseek(fp, (long) dst, SEEK_SET) != 0) {
+ ALOGD("filemove dst seek %ld failed\n", (long) dst);
+ return UNKNOWN_ERROR;
+ }
+
+ if (fwrite(readBuf, 1, getSize, fp) != getSize) {
+ ALOGD("filemove write %ld off=%ld failed\n",
+ (long) getSize, (long) dst);
+ return UNKNOWN_ERROR;
+ }
+
+ src += getSize;
+ dst += getSize;
+ n -= getSize;
+ }
+ } else {
+ /* shift stuff toward end of file; must read from end */
+ assert(false); // write this someday, maybe
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+
+/*
+ * Get the modification time from a file descriptor.
+ */
+time_t ZipFile::getModTime(int fd)
+{
+ struct stat sb;
+
+ if (fstat(fd, &sb) < 0) {
+ ALOGD("HEY: fstat on fd %d failed\n", fd);
+ return (time_t) -1;
+ }
+
+ return sb.st_mtime;
+}
+
+
+#if 0 /* this is a bad idea */
+/*
+ * Get a copy of the Zip file descriptor.
+ *
+ * We don't allow this if the file was opened read-write because we tend
+ * to leave the file contents in an uncertain state between calls to
+ * flush(). The duplicated file descriptor should only be valid for reads.
+ */
+int ZipFile::getZipFd(void) const
+{
+ if (!mReadOnly)
+ return INVALID_OPERATION;
+ assert(mZipFp != NULL);
+
+ int fd;
+ fd = dup(fileno(mZipFp));
+ if (fd < 0) {
+ ALOGD("didn't work, errno=%d\n", errno);
+ }
+
+ return fd;
+}
+#endif
+
+
+#if 0
+/*
+ * Expand data.
+ */
+bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const
+{
+ return false;
+}
+#endif
+
+// free the memory when you're done
+void* ZipFile::uncompress(const ZipEntry* entry)
+{
+ size_t unlen = entry->getUncompressedLen();
+ size_t clen = entry->getCompressedLen();
+
+ void* buf = malloc(unlen);
+ if (buf == NULL) {
+ return NULL;
+ }
+
+ fseek(mZipFp, 0, SEEK_SET);
+
+ off_t offset = entry->getFileOffset();
+ if (fseek(mZipFp, offset, SEEK_SET) != 0) {
+ goto bail;
+ }
+
+ switch (entry->getCompressionMethod())
+ {
+ case ZipEntry::kCompressStored: {
+ ssize_t amt = fread(buf, 1, unlen, mZipFp);
+ if (amt != (ssize_t)unlen) {
+ goto bail;
+ }
+#if 0
+ printf("data...\n");
+ const unsigned char* p = (unsigned char*)buf;
+ const unsigned char* end = p+unlen;
+ for (int i=0; i<32 && p < end; i++) {
+ printf("0x%08x ", (int)(offset+(i*0x10)));
+ for (int j=0; j<0x10 && p < end; j++) {
+ printf(" %02x", *p);
+ p++;
+ }
+ printf("\n");
+ }
+#endif
+
+ }
+ break;
+ case ZipEntry::kCompressDeflated: {
+ if (!ZipUtils::inflateToBuffer(mZipFp, buf, unlen, clen)) {
+ goto bail;
+ }
+ }
+ break;
+ default:
+ goto bail;
+ }
+ return buf;
+
+bail:
+ free(buf);
+ return NULL;
+}
+
+
+/*
+ * ===========================================================================
+ * ZipFile::EndOfCentralDir
+ * ===========================================================================
+ */
+
+/*
+ * Read the end-of-central-dir fields.
+ *
+ * "buf" should be positioned at the EOCD signature, and should contain
+ * the entire EOCD area including the comment.
+ */
+status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len)
+{
+ /* don't allow re-use */
+ assert(mComment == NULL);
+
+ if (len < kEOCDLen) {
+ /* looks like ZIP file got truncated */
+ ALOGD(" Zip EOCD: expected >= %d bytes, found %d\n",
+ kEOCDLen, len);
+ return INVALID_OPERATION;
+ }
+
+ /* this should probably be an assert() */
+ if (ZipEntry::getLongLE(&buf[0x00]) != kSignature)
+ return UNKNOWN_ERROR;
+
+ mDiskNumber = ZipEntry::getShortLE(&buf[0x04]);
+ mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]);
+ mNumEntries = ZipEntry::getShortLE(&buf[0x08]);
+ mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]);
+ mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]);
+ mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]);
+ mCommentLen = ZipEntry::getShortLE(&buf[0x14]);
+
+ // TODO: validate mCentralDirOffset
+
+ if (mCommentLen > 0) {
+ if (kEOCDLen + mCommentLen > len) {
+ ALOGD("EOCD(%d) + comment(%d) exceeds len (%d)\n",
+ kEOCDLen, mCommentLen, len);
+ return UNKNOWN_ERROR;
+ }
+ mComment = new unsigned char[mCommentLen];
+ memcpy(mComment, buf + kEOCDLen, mCommentLen);
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Write an end-of-central-directory section.
+ */
+status_t ZipFile::EndOfCentralDir::write(FILE* fp)
+{
+ unsigned char buf[kEOCDLen];
+
+ ZipEntry::putLongLE(&buf[0x00], kSignature);
+ ZipEntry::putShortLE(&buf[0x04], mDiskNumber);
+ ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir);
+ ZipEntry::putShortLE(&buf[0x08], mNumEntries);
+ ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries);
+ ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize);
+ ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset);
+ ZipEntry::putShortLE(&buf[0x14], mCommentLen);
+
+ if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen)
+ return UNKNOWN_ERROR;
+ if (mCommentLen > 0) {
+ assert(mComment != NULL);
+ if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen)
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Dump the contents of an EndOfCentralDir object.
+ */
+void ZipFile::EndOfCentralDir::dump(void) const
+{
+ ALOGD(" EndOfCentralDir contents:\n");
+ ALOGD(" diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n",
+ mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries);
+ ALOGD(" centDirSize=%lu centDirOff=%lu commentLen=%u\n",
+ mCentralDirSize, mCentralDirOffset, mCommentLen);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ZipFile.h b/tools/aapt2/ZipFile.h
new file mode 100644
index 0000000..9de92dd
--- /dev/null
+++ b/tools/aapt2/ZipFile.h
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// General-purpose Zip archive access. This class allows both reading and
+// writing to Zip archives, including deletion of existing entries.
+//
+#ifndef __LIBS_ZIPFILE_H
+#define __LIBS_ZIPFILE_H
+
+#include "BigBuffer.h"
+#include "ZipEntry.h"
+
+#include <stdio.h>
+#include <utils/Errors.h>
+#include <vector>
+
+namespace aapt {
+
+using android::status_t;
+
+/*
+ * Manipulate a Zip archive.
+ *
+ * Some changes will not be visible in the until until "flush" is called.
+ *
+ * The correct way to update a file archive is to make all changes to a
+ * copy of the archive in a temporary file, and then unlink/rename over
+ * the original after everything completes. Because we're only interested
+ * in using this for packaging, we don't worry about such things. Crashing
+ * after making changes and before flush() completes could leave us with
+ * an unusable Zip archive.
+ */
+class ZipFile {
+public:
+ ZipFile(void)
+ : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false)
+ {}
+ ~ZipFile(void) {
+ if (!mReadOnly)
+ flush();
+ if (mZipFp != NULL)
+ fclose(mZipFp);
+ discardEntries();
+ }
+
+ /*
+ * Open a new or existing archive.
+ */
+ enum {
+ kOpenReadOnly = 0x01,
+ kOpenReadWrite = 0x02,
+ kOpenCreate = 0x04, // create if it doesn't exist
+ kOpenTruncate = 0x08, // if it exists, empty it
+ };
+ status_t open(const char* zipFileName, int flags);
+
+ /*
+ * Add a file to the end of the archive. Specify whether you want the
+ * library to try to store it compressed.
+ *
+ * If "storageName" is specified, the archive will use that instead
+ * of "fileName".
+ *
+ * If there is already an entry with the same name, the call fails.
+ * Existing entries with the same name must be removed first.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const char* fileName, int compressionMethod,
+ ZipEntry** ppEntry)
+ {
+ return add(fileName, fileName, compressionMethod, ppEntry);
+ }
+ status_t add(const char* fileName, const char* storageName,
+ int compressionMethod, ZipEntry** ppEntry)
+ {
+ return addCommon(fileName, NULL, 0, storageName,
+ ZipEntry::kCompressStored,
+ compressionMethod, ppEntry);
+ }
+
+ /*
+ * Add a file that is already compressed with gzip.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t addGzip(const char* fileName, const char* storageName,
+ ZipEntry** ppEntry)
+ {
+ return addCommon(fileName, NULL, 0, storageName,
+ ZipEntry::kCompressDeflated,
+ ZipEntry::kCompressDeflated, ppEntry);
+ }
+
+ /*
+ * Add a file from an in-memory data buffer.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const void* data, size_t size, const char* storageName,
+ int compressionMethod, ZipEntry** ppEntry)
+ {
+ return addCommon(NULL, data, size, storageName,
+ ZipEntry::kCompressStored,
+ compressionMethod, ppEntry);
+ }
+
+ status_t add(const BigBuffer& data, const char* storageName,
+ int compressionMethod, ZipEntry** ppEntry);
+
+ /*
+ * Add an entry by copying it from another zip file. If storageName is
+ * non-NULL, the entry will be inserted with the name storageName, otherwise
+ * it will have the same name as the source entry. If "padding" is
+ * nonzero, the specified number of bytes will be added to the "extra"
+ * field in the header.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
+ const char* storageName, int padding, ZipEntry** ppEntry);
+
+ /*
+ * Mark an entry as having been removed. It is not actually deleted
+ * from the archive or our internal data structures until flush() is
+ * called.
+ */
+ status_t remove(ZipEntry* pEntry);
+
+ /*
+ * Flush changes. If mNeedCDRewrite is set, this writes the central dir.
+ */
+ status_t flush(void);
+
+ /*
+ * Expand the data into the buffer provided. The buffer must hold
+ * at least <uncompressed len> bytes. Variation expands directly
+ * to a file.
+ *
+ * Returns "false" if an error was encountered in the compressed data.
+ */
+ //bool uncompress(const ZipEntry* pEntry, void* buf) const;
+ //bool uncompress(const ZipEntry* pEntry, FILE* fp) const;
+ void* uncompress(const ZipEntry* pEntry);
+
+ /*
+ * Get an entry, by name. Returns NULL if not found.
+ *
+ * Does not return entries pending deletion.
+ */
+ ZipEntry* getEntryByName(const char* fileName) const;
+
+ /*
+ * Get the Nth entry in the archive.
+ *
+ * This will return an entry that is pending deletion.
+ */
+ int getNumEntries(void) const { return mEntries.size(); }
+ ZipEntry* getEntryByIndex(int idx) const;
+
+private:
+ /* these are private and not defined */
+ ZipFile(const ZipFile& src);
+ ZipFile& operator=(const ZipFile& src);
+
+ class EndOfCentralDir {
+ public:
+ EndOfCentralDir(void) :
+ mDiskNumber(0),
+ mDiskWithCentralDir(0),
+ mNumEntries(0),
+ mTotalNumEntries(0),
+ mCentralDirSize(0),
+ mCentralDirOffset(0),
+ mCommentLen(0),
+ mComment(NULL)
+ {}
+ virtual ~EndOfCentralDir(void) {
+ delete[] mComment;
+ }
+
+ status_t readBuf(const unsigned char* buf, int len);
+ status_t write(FILE* fp);
+
+ //unsigned long mSignature;
+ unsigned short mDiskNumber;
+ unsigned short mDiskWithCentralDir;
+ unsigned short mNumEntries;
+ unsigned short mTotalNumEntries;
+ unsigned long mCentralDirSize;
+ unsigned long mCentralDirOffset; // offset from first disk
+ unsigned short mCommentLen;
+ unsigned char* mComment;
+
+ enum {
+ kSignature = 0x06054b50,
+ kEOCDLen = 22, // EndOfCentralDir len, excl. comment
+
+ kMaxCommentLen = 65535, // longest possible in ushort
+ kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen,
+
+ };
+
+ void dump(void) const;
+ };
+
+
+ /* read all entries in the central dir */
+ status_t readCentralDir(void);
+
+ /* crunch deleted entries out */
+ status_t crunchArchive(void);
+
+ /* clean up mEntries */
+ void discardEntries(void);
+
+ /* common handler for all "add" functions */
+ status_t addCommon(const char* fileName, const void* data, size_t size,
+ const char* storageName, int sourceType, int compressionMethod,
+ ZipEntry** ppEntry);
+
+ /* copy all of "srcFp" into "dstFp" */
+ status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32);
+ /* copy all of "data" into "dstFp" */
+ status_t copyDataToFp(FILE* dstFp,
+ const void* data, size_t size, unsigned long* pCRC32);
+ /* copy some of "srcFp" into "dstFp" */
+ status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
+ unsigned long* pCRC32);
+ /* like memmove(), but on parts of a single file */
+ status_t filemove(FILE* fp, off_t dest, off_t src, size_t n);
+ /* compress all of "srcFp" into "dstFp", using Deflate */
+ status_t compressFpToFp(FILE* dstFp, FILE* srcFp,
+ const void* data, size_t size, unsigned long* pCRC32);
+
+ /* get modification date from a file descriptor */
+ time_t getModTime(int fd);
+
+ /*
+ * We use stdio FILE*, which gives us buffering but makes dealing
+ * with files >2GB awkward. Until we support Zip64, we're fine.
+ */
+ FILE* mZipFp; // Zip file pointer
+
+ /* one of these per file */
+ EndOfCentralDir mEOCD;
+
+ /* did we open this read-only? */
+ bool mReadOnly;
+
+ /* set this when we trash the central dir */
+ bool mNeedCDRewrite;
+
+ /*
+ * One ZipEntry per entry in the zip file. I'm using pointers instead
+ * of objects because it's easier than making operator= work for the
+ * classes and sub-classes.
+ */
+ std::vector<ZipEntry*> mEntries;
+};
+
+}; // namespace aapt
+
+#endif // __LIBS_ZIPFILE_H
diff --git a/tools/aapt2/data/AndroidManifest.xml b/tools/aapt2/data/AndroidManifest.xml
new file mode 100644
index 0000000..8533c28
--- /dev/null
+++ b/tools/aapt2/data/AndroidManifest.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.app">
+ <application
+ android:name=".Activity">
+ </application>
+</manifest>
diff --git a/tools/aapt2/data/Makefile b/tools/aapt2/data/Makefile
new file mode 100644
index 0000000..3387135
--- /dev/null
+++ b/tools/aapt2/data/Makefile
@@ -0,0 +1,83 @@
+##
+# Environment dependent variables
+##
+
+AAPT := aapt2
+ZIPALIGN := zipalign -f 4
+FRAMEWORK := ../../../../../out/target/common/obj/APPS/framework-res_intermediates/package-export.apk
+
+##
+# Project depenedent variables
+##
+
+LOCAL_PACKAGE := com.android.app
+LOCAL_RESOURCE_DIR := res
+LOCAL_LIBS := lib/out/package.apk
+LOCAL_OUT := out
+LOCAL_GEN := out/gen
+LOCAL_PROGUARD := out/proguard.rule
+
+##
+# AAPT2 custom rules.
+##
+
+PRIVATE_APK_UNALIGNED := $(LOCAL_OUT)/package-unaligned.apk
+PRIVATE_APK_ALIGNED := $(LOCAL_OUT)/package.apk
+
+# Eg: framework.apk, etc.
+PRIVATE_INCLUDES := $(FRAMEWORK)
+$(info PRIVATE_INCLUDES = $(PRIVATE_INCLUDES))
+
+# Eg: gen/com/android/app/R.java
+PRIVATE_R_JAVA := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java
+$(info PRIVATE_R_JAVA = $(PRIVATE_R_JAVA))
+
+# Eg: res/drawable/icon.png, res/values/styles.xml
+PRIVATE_RESOURCES := $(shell find $(LOCAL_RESOURCE_DIR) -mindepth 1 -maxdepth 2 -type f)
+$(info PRIVATE_RESOURCES = $(PRIVATE_RESOURCES))
+
+# Eg: drawable, values, layouts
+PRIVATE_RESOURCE_TYPES := \
+ $(patsubst $(LOCAL_RESOURCE_DIR)/%/,%,$(sort $(dir $(PRIVATE_RESOURCES))))
+$(info PRIVATE_RESOURCE_TYPES = $(PRIVATE_RESOURCE_TYPES))
+
+# Eg: out/values-v4.apk, out/drawable-xhdpi.apk
+PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.apk,$(PRIVATE_RESOURCE_TYPES))
+$(info PRIVATE_INTERMEDIATE_TABLES = $(PRIVATE_INTERMEDIATE_TABLES))
+
+# Generates rules for collect phase.
+# $1: Resource type (values-v4)
+# returns: out/values-v4.apk: res/values-v4/styles.xml res/values-v4/colors.xml
+define make-collect-rule
+$(LOCAL_OUT)/$1.apk: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES))
+ $(AAPT) compile --package $(LOCAL_PACKAGE) -o $$@ $$^
+endef
+
+# Collect: out/values-v4.apk <- res/values-v4/styles.xml res/values-v4/colors.xml
+$(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d)))
+
+# Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk
+$(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_INCLUDES) $(LOCAL_LIBS) AndroidManifest.xml
+ $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_INCLUDES)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) $(LOCAL_LIBS) --proguard $(LOCAL_PROGUARD) -v
+
+# R.java: gen/com/android/app/R.java <- out/resources.arsc
+# No action since R.java is generated when out/resources.arsc is.
+$(PRIVATE_R_JAVA): $(PRIVATE_APK_UNALIGNED)
+
+# Assemble: zip out/resources.arsc AndroidManifest.xml and res/**/*
+$(PRIVATE_APK_ALIGNED): $(PRIVATE_APK_UNALIGNED)
+ $(ZIPALIGN) $< $@
+
+# Create the out directory if needed.
+dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT))
+
+.PHONY: java
+java: $(PRIVATE_R_JAVA)
+
+.PHONY: assemble
+assemble: $(PRIVATE_APK_ALIGNED)
+
+.PHONY: all
+all: assemble java
+
+.DEFAULT_GOAL := all
diff --git a/tools/aapt2/data/lib/AndroidManifest.xml b/tools/aapt2/data/lib/AndroidManifest.xml
new file mode 100644
index 0000000..08b468e
--- /dev/null
+++ b/tools/aapt2/data/lib/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.appcompat">
+
+ <uses-feature android:name="bloooop" />
+</manifest>
diff --git a/tools/aapt2/data/lib/Makefile b/tools/aapt2/data/lib/Makefile
new file mode 100644
index 0000000..372c225
--- /dev/null
+++ b/tools/aapt2/data/lib/Makefile
@@ -0,0 +1,81 @@
+##
+# Environment dependent variables
+##
+
+AAPT := aapt2
+ZIPALIGN := zipalign -f 4
+FRAMEWORK := ../../../../../../out/target/common/obj/APPS/framework-res_intermediates/package-export.apk
+
+##
+# Project depenedent variables
+##
+
+LOCAL_PACKAGE := android.appcompat
+LOCAL_RESOURCE_DIR := res
+LOCAL_OUT := out
+LOCAL_GEN := out/gen
+
+##
+# AAPT2 custom rules.
+##
+
+PRIVATE_APK_UNALIGNED := $(LOCAL_OUT)/package-unaligned.apk
+PRIVATE_APK_ALIGNED := $(LOCAL_OUT)/package.apk
+
+# Eg: framework.apk, etc.
+PRIVATE_LIBS := $(FRAMEWORK)
+$(info PRIVATE_LIBS = $(PRIVATE_LIBS))
+
+# Eg: gen/com/android/app/R.java
+PRIVATE_R_JAVA := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java
+$(info PRIVATE_R_JAVA = $(PRIVATE_R_JAVA))
+
+# Eg: res/drawable/icon.png, res/values/styles.xml
+PRIVATE_RESOURCES := $(shell find $(LOCAL_RESOURCE_DIR) -mindepth 1 -maxdepth 2 -type f)
+$(info PRIVATE_RESOURCES = $(PRIVATE_RESOURCES))
+
+# Eg: drawable, values, layouts
+PRIVATE_RESOURCE_TYPES := \
+ $(patsubst $(LOCAL_RESOURCE_DIR)/%/,%,$(sort $(dir $(PRIVATE_RESOURCES))))
+$(info PRIVATE_RESOURCE_TYPES = $(PRIVATE_RESOURCE_TYPES))
+
+# Eg: out/values-v4.apk, out/drawable-xhdpi.apk
+PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.apk,$(PRIVATE_RESOURCE_TYPES))
+$(info PRIVATE_INTERMEDIATE_TABLES = $(PRIVATE_INTERMEDIATE_TABLES))
+
+# Generates rules for collect phase.
+# $1: Resource type (values-v4)
+# returns: out/values-v4.apk: res/values-v4/styles.xml res/values-v4/colors.xml
+define make-collect-rule
+$(LOCAL_OUT)/$1.apk: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES))
+ $(AAPT) compile --package $(LOCAL_PACKAGE) -o $$@ $$^
+endef
+
+# Collect: out/values-v4.apk <- res/values-v4/styles.xml res/values-v4/colors.xml
+$(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d)))
+
+# Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk
+$(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_LIBS) AndroidManifest.xml
+ $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_LIBS)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) --static-lib
+
+# R.java: gen/com/android/app/R.java <- out/resources.arsc
+# No action since R.java is generated when out/resources.arsc is.
+$(PRIVATE_R_JAVA): $(PRIVATE_APK_UNALIGNED)
+
+# Assemble: zip out/resources.arsc AndroidManifest.xml and res/**/*
+$(PRIVATE_APK_ALIGNED): $(PRIVATE_APK_UNALIGNED)
+ $(ZIPALIGN) $< $@
+
+# Create the out directory if needed.
+dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT))
+
+.PHONY: java
+java: $(PRIVATE_R_JAVA)
+
+.PHONY: assemble
+assemble: $(PRIVATE_APK_ALIGNED)
+
+.PHONY: all
+all: assemble java
+
+.DEFAULT_GOAL := all
diff --git a/tools/aapt2/data/lib/res/layout/main.xml b/tools/aapt2/data/lib/res/layout/main.xml
new file mode 100644
index 0000000..187ed2d
--- /dev/null
+++ b/tools/aapt2/data/lib/res/layout/main.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
diff --git a/tools/aapt2/data/lib/res/raw/hello.txt b/tools/aapt2/data/lib/res/raw/hello.txt
new file mode 100644
index 0000000..44fc22b
--- /dev/null
+++ b/tools/aapt2/data/lib/res/raw/hello.txt
@@ -0,0 +1 @@
+Oh howdy there
diff --git a/tools/aapt2/data/lib/res/values/styles.xml b/tools/aapt2/data/lib/res/values/styles.xml
new file mode 100644
index 0000000..4ce6333
--- /dev/null
+++ b/tools/aapt2/data/lib/res/values/styles.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style name="Platform.AppCompat" parent="@android:style/Theme">
+ <item name="android:windowNoTitle">true</item>
+ </style>
+
+ <bool name="allow">true</bool>
+</resources>
diff --git a/tools/aapt2/data/res/drawable/icon.png b/tools/aapt2/data/res/drawable/icon.png
new file mode 100644
index 0000000..4bff9b9
--- /dev/null
+++ b/tools/aapt2/data/res/drawable/icon.png
Binary files differ
diff --git a/tools/aapt2/data/res/drawable/image.xml b/tools/aapt2/data/res/drawable/image.xml
new file mode 100644
index 0000000..9b38739
--- /dev/null
+++ b/tools/aapt2/data/res/drawable/image.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector />
diff --git a/tools/aapt2/data/res/drawable/test.9.png b/tools/aapt2/data/res/drawable/test.9.png
new file mode 100644
index 0000000..33daa11
--- /dev/null
+++ b/tools/aapt2/data/res/drawable/test.9.png
Binary files differ
diff --git a/tools/aapt2/data/res/layout/main.xml b/tools/aapt2/data/res/layout/main.xml
new file mode 100644
index 0000000..50a51d9
--- /dev/null
+++ b/tools/aapt2/data/res/layout/main.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:support="http://schemas.android.com/apk/res/android.appcompat"
+ android:id="@+id/view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <fragment class="android.test.sample.App$Inner" />
+
+ <variable name="user" type="com.android.User" />
+
+ <View xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/me"
+ android:layout_width="1dp"
+ android:onClick="doClick"
+ android:text="@{user.name}"
+ android:layout_height="match_parent"
+ app:layout_width="@support:bool/allow"
+ app:flags="complex|weak"
+ android:colorAccent="#ffffff"/>
+</LinearLayout>
diff --git a/tools/aapt2/data/res/values-v4/styles.xml b/tools/aapt2/data/res/values-v4/styles.xml
new file mode 100644
index 0000000..979a82a
--- /dev/null
+++ b/tools/aapt2/data/res/values-v4/styles.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style name="App" parent="android:Theme.Material">
+ <item name="android:colorAccent">@color/accent</item>
+ <item name="android:text">Hey</item>
+ </style>
+</resources>
diff --git a/tools/aapt2/data/res/values/colors.xml b/tools/aapt2/data/res/values/colors.xml
new file mode 100644
index 0000000..89db5fb
--- /dev/null
+++ b/tools/aapt2/data/res/values/colors.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="primary">#f44336</color>
+ <color name="primary_dark">#b71c1c</color>
+ <color name="accent">#fdd835</color>
+</resources>
diff --git a/tools/aapt2/data/res/values/styles.xml b/tools/aapt2/data/res/values/styles.xml
new file mode 100644
index 0000000..d0b19a3
--- /dev/null
+++ b/tools/aapt2/data/res/values/styles.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:lib="http://schemas.android.com/apk/res/android.appcompat">
+ <style name="App" parent="android.appcompat:Platform.AppCompat">
+ <item name="android:background">@color/primary</item>
+ <item name="android:colorPrimary">@color/primary</item>
+ <item name="android:colorPrimaryDark">@color/primary_dark</item>
+ <item name="android:colorAccent">@color/accent</item>
+ </style>
+ <attr name="custom" format="reference" />
+ <style name="Pop">
+ <item name="custom">@drawable/image</item>
+ <item name="android:focusable">@lib:bool/allow</item>
+ </style>
+ <string name="yo">@string/wow</string>
+
+ <declare-styleable name="View">
+ <attr name="custom" />
+ <attr name="decor">
+ <enum name="no-border" value="0"/>
+ <enum name="border" value="1"/>
+ <enum name="shadow" value="2"/>
+ </attr>
+ </declare-styleable>
+
+</resources>
diff --git a/tools/aapt2/data/res/values/test.xml b/tools/aapt2/data/res/values/test.xml
new file mode 100644
index 0000000..d3ead34
--- /dev/null
+++ b/tools/aapt2/data/res/values/test.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="hooha"><font bgcolor="#ffffff">Hey guys!</font> <xliff:g>My</xliff:g> name is <b>Adam</b>. How <b><i>are</i></b> you?</string>
+ <public name="hooha" type="string" id="0x7f020001"/>
+ <string name="wow">@android:string/ok</string>
+ <public name="image" type="drawable" id="0x7f060000" />
+ <attr name="layout_width" format="boolean" />
+ <attr name="flags">
+ <flag name="complex" value="1" />
+ <flag name="pub" value="2" />
+ <flag name="weak" value="4" />
+ </attr>
+</resources>
diff --git a/tools/aapt2/data/resources.arsc b/tools/aapt2/data/resources.arsc
new file mode 100644
index 0000000..6a416df
--- /dev/null
+++ b/tools/aapt2/data/resources.arsc
Binary files differ
diff --git a/tools/aapt2/data/resources_base.arsc b/tools/aapt2/data/resources_base.arsc
new file mode 100644
index 0000000..f9d0610
--- /dev/null
+++ b/tools/aapt2/data/resources_base.arsc
Binary files differ
diff --git a/tools/aapt2/data/resources_hdpi.arsc b/tools/aapt2/data/resources_hdpi.arsc
new file mode 100644
index 0000000..97232a3
--- /dev/null
+++ b/tools/aapt2/data/resources_hdpi.arsc
Binary files differ
diff --git a/tools/aapt2/process.dot b/tools/aapt2/process.dot
new file mode 100644
index 0000000..4741952
--- /dev/null
+++ b/tools/aapt2/process.dot
@@ -0,0 +1,108 @@
+digraph aapt {
+ out_package [label="out/default/package.apk"];
+ out_fr_package [label="out/fr/package.apk"];
+ out_table_aligned [label="out/default/resources-aligned.arsc"];
+ out_table_fr_aligned [label="out/fr/resources-aligned.arsc"];
+ out_res_layout_main_xml [label="out/res/layout/main.xml"];
+ out_res_layout_v21_main_xml [color=red,label="out/res/layout-v21/main.xml"];
+ out_res_layout_fr_main_xml [label="out/res/layout-fr/main.xml"];
+ out_res_layout_fr_v21_main_xml [color=red,label="out/res/layout-fr-v21/main.xml"];
+ out_table [label="out/default/resources.arsc"];
+ out_fr_table [label="out/fr/resources.arsc"];
+ out_values_table [label="out/values/resources.arsc"];
+ out_layout_table [label="out/layout/resources.arsc"];
+ out_values_fr_table [label="out/values-fr/resources.arsc"];
+ out_layout_fr_table [label="out/layout-fr/resources.arsc"];
+ res_values_strings_xml [label="res/values/strings.xml"];
+ res_values_attrs_xml [label="res/values/attrs.xml"];
+ res_layout_main_xml [label="res/layout/main.xml"];
+ res_layout_fr_main_xml [label="res/layout-fr/main.xml"];
+ res_values_fr_strings_xml [label="res/values-fr/strings.xml"];
+
+ lib_apk_resources_arsc [label="lib.apk:resources.arsc",color=green];
+ lib_apk_res_layout_main_xml [label="lib.apk:res/layout/main.xml",color=green];
+ lib_apk_res_drawable_icon_png [label="lib.apk:res/drawable/icon.png",color=green];
+ lib_apk_fr_res_layout_main_xml [label="lib.apk:res/layout-fr/main.xml",color=green];
+ lib_apk_fr_res_drawable_icon_png [label="lib.apk:res/drawable-fr/icon.png",color=green];
+ out_res_layout_lib_main_xml [label="out/res/layout/lib-main.xml"];
+
+ out_package -> package_default;
+ out_fr_package -> package_fr;
+
+ package_default [shape=box,label="Assemble",color=blue];
+ package_default -> out_table_aligned;
+ package_default -> out_res_layout_main_xml;
+ package_default -> out_res_layout_v21_main_xml [color=red];
+ package_default -> out_res_layout_lib_main_xml;
+
+ package_fr [shape=box,label="Assemble",color=blue];
+ package_fr -> out_table_fr_aligned;
+ package_fr -> out_res_layout_fr_main_xml;
+ package_fr -> out_res_layout_fr_v21_main_xml [color=red];
+
+ out_table_aligned -> align_tables;
+ out_table_fr_aligned -> align_tables;
+
+ align_tables [shape=box,label="Align",color=blue];
+ align_tables -> out_table;
+ align_tables -> out_fr_table;
+
+ out_table -> link_tables;
+
+ link_tables [shape=box,label="Link",color=blue];
+ link_tables -> out_values_table;
+ link_tables -> out_layout_table;
+ link_tables -> lib_apk_resources_arsc;
+
+ out_values_table -> compile_values;
+
+ compile_values [shape=box,label="Collect",color=blue];
+ compile_values -> res_values_strings_xml;
+ compile_values -> res_values_attrs_xml;
+
+ out_layout_table -> collect_xml;
+
+ collect_xml [shape=box,label="Collect",color=blue];
+ collect_xml -> res_layout_main_xml;
+
+ out_fr_table -> link_fr_tables;
+
+ link_fr_tables [shape=box,label="Link",color=blue];
+ link_fr_tables -> out_values_fr_table;
+ link_fr_tables -> out_layout_fr_table;
+ link_fr_tables -> lib_apk_resources_arsc;
+
+ out_values_fr_table -> compile_values_fr;
+
+ compile_values_fr [shape=box,label="Collect",color=blue];
+ compile_values_fr -> res_values_fr_strings_xml;
+
+ out_layout_fr_table -> collect_xml_fr;
+
+ collect_xml_fr [shape=box,label="Collect",color=blue];
+ collect_xml_fr -> res_layout_fr_main_xml;
+
+ compile_res_layout_main_xml [shape=box,label="Compile",color=blue];
+
+ out_res_layout_main_xml -> compile_res_layout_main_xml;
+
+ out_res_layout_v21_main_xml -> compile_res_layout_main_xml [color=red];
+
+ compile_res_layout_main_xml -> res_layout_main_xml;
+ compile_res_layout_main_xml -> out_table_aligned;
+
+ compile_res_layout_fr_main_xml [shape=box,label="Compile",color=blue];
+
+ out_res_layout_fr_main_xml -> compile_res_layout_fr_main_xml;
+
+ out_res_layout_fr_v21_main_xml -> compile_res_layout_fr_main_xml [color=red];
+
+ compile_res_layout_fr_main_xml -> res_layout_fr_main_xml;
+ compile_res_layout_fr_main_xml -> out_table_fr_aligned;
+
+ out_res_layout_lib_main_xml -> compile_res_layout_lib_main_xml;
+
+ compile_res_layout_lib_main_xml [shape=box,label="Compile",color=blue];
+ compile_res_layout_lib_main_xml -> out_table_aligned;
+ compile_res_layout_lib_main_xml -> lib_apk_res_layout_main_xml;
+}
diff --git a/tools/aapt2/public_attr_map.py b/tools/aapt2/public_attr_map.py
new file mode 100644
index 0000000..92136a8
--- /dev/null
+++ b/tools/aapt2/public_attr_map.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+
+import sys
+import xml.etree.ElementTree as ET
+
+def findSdkLevelForAttribute(id):
+ intId = int(id, 16)
+ packageId = 0x000000ff & (intId >> 24)
+ typeId = 0x000000ff & (intId >> 16)
+ entryId = 0x0000ffff & intId
+
+ if packageId != 0x01 or typeId != 0x01:
+ return 0
+
+ levels = [(1, 0x021c), (2, 0x021d), (3, 0x0269), (4, 0x028d),
+ (5, 0x02ad), (6, 0x02b3), (7, 0x02b5), (8, 0x02bd),
+ (9, 0x02cb), (11, 0x0361), (12, 0x0366), (13, 0x03a6),
+ (16, 0x03ae), (17, 0x03cc), (18, 0x03da), (19, 0x03f1),
+ (20, 0x03f6), (21, 0x04ce)]
+ for level, attrEntryId in levels:
+ if entryId <= attrEntryId:
+ return level
+ return 22
+
+
+tree = None
+with open(sys.argv[1], 'rt') as f:
+ tree = ET.parse(f)
+
+attrs = []
+for node in tree.iter('public'):
+ if node.get('type') == 'attr':
+ sdkLevel = findSdkLevelForAttribute(node.get('id', '0'))
+ if sdkLevel > 1 and sdkLevel < 22:
+ attrs.append("{{ u\"{}\", {} }}".format(node.get('name'), sdkLevel))
+
+print "#include <string>"
+print "#include <unordered_map>"
+print
+print "namespace aapt {"
+print
+print "static std::unordered_map<std::u16string, size_t> sAttrMap = {"
+print ",\n ".join(attrs)
+print "};"
+print
+print "size_t findAttributeSdkLevel(const std::u16string& name) {"
+print " auto iter = sAttrMap.find(name);"
+print " if (iter != sAttrMap.end()) {"
+print " return iter->second;"
+print " }"
+print " return 0;"
+print "}"
+print
+print "} // namespace aapt"
+print
diff --git a/tools/aapt2/todo.txt b/tools/aapt2/todo.txt
new file mode 100644
index 0000000..acc8bfb
--- /dev/null
+++ b/tools/aapt2/todo.txt
@@ -0,0 +1,29 @@
+XML Files
+X Collect declared IDs
+X Build StringPool
+X Flatten
+
+Resource Table Operations
+X Build Resource Table (with StringPool) from XML.
+X Modify Resource Table.
+X - Copy and transform resources.
+X - Pre-17/21 attr correction.
+X Perform analysis of types.
+X Flatten.
+X Assign resource IDs.
+X Assign public resource IDs.
+X Merge resource tables
+- Assign private attributes to different typespace.
+- Align resource tables
+
+Splits
+- Collect all resources (ids from layouts).
+- Generate resource table from base resources.
+- Generate resource table from individual resources of the required type.
+- Align resource tables (same type/name = same ID).
+
+Fat Apk
+X Collect all resources (ids from layouts).
+X Generate resource tables for all configurations.
+- Align individual resource tables.
+- Merge resource tables.
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index 393d2ec..df76bc9 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -26,14 +26,17 @@ $ git blame api/current.txt -t -e > /tmp/currentblame.txt
$ apilint.py /tmp/currentblame.txt previous.txt --no-color
"""
-import re, sys, collections, traceback
+import re, sys, collections, traceback, argparse
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
+ALLOW_GOOGLE = False
+USE_COLOR = True
+
def format(fg=None, bg=None, bright=False, bold=False, dim=False, reset=False):
# manually derived from http://en.wikipedia.org/wiki/ANSI_escape_code#Codes
- if "--no-color" in sys.argv: return ""
+ if not USE_COLOR: return ""
codes = []
if reset: codes.append("0")
else:
@@ -48,8 +51,9 @@ def format(fg=None, bg=None, bright=False, bold=False, dim=False, reset=False):
class Field():
- def __init__(self, clazz, raw, blame):
+ def __init__(self, clazz, line, raw, blame):
self.clazz = clazz
+ self.line = line
self.raw = raw.strip(" {;")
self.blame = blame
@@ -73,8 +77,9 @@ class Field():
class Method():
- def __init__(self, clazz, raw, blame):
+ def __init__(self, clazz, line, raw, blame):
self.clazz = clazz
+ self.line = line
self.raw = raw.strip(" {;")
self.blame = blame
@@ -110,8 +115,9 @@ class Method():
class Class():
- def __init__(self, pkg, raw, blame):
+ def __init__(self, pkg, line, raw, blame):
self.pkg = pkg
+ self.line = line
self.raw = raw.strip(" {;")
self.blame = blame
self.ctors = []
@@ -129,10 +135,14 @@ class Class():
if "extends" in raw:
self.extends = raw[raw.index("extends")+1]
+ self.extends_path = self.extends.split(".")
else:
self.extends = None
+ self.extends_path = []
self.fullname = self.pkg.name + "." + self.fullname
+ self.fullname_path = self.fullname.split(".")
+
self.name = self.fullname[self.fullname.rindex(".")+1:]
def __repr__(self):
@@ -140,75 +150,110 @@ class Class():
class Package():
- def __init__(self, raw, blame):
+ def __init__(self, line, raw, blame):
+ self.line = line
self.raw = raw.strip(" {;")
self.blame = blame
raw = raw.split()
self.name = raw[raw.index("package")+1]
+ self.name_path = self.name.split(".")
def __repr__(self):
return self.raw
-def parse_api(fn):
+def _parse_stream(f, clazz_cb=None):
+ line = 0
api = {}
pkg = None
clazz = None
blame = None
re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
-
- with open(fn) as f:
- for raw in f.readlines():
- raw = raw.rstrip()
- match = re_blame.match(raw)
- if match is not None:
- blame = match.groups()[0:2]
- raw = match.groups()[2]
- else:
- blame = None
-
- if raw.startswith("package"):
- pkg = Package(raw, blame)
- elif raw.startswith(" ") and raw.endswith("{"):
- clazz = Class(pkg, raw, blame)
+ for raw in f:
+ line += 1
+ raw = raw.rstrip()
+ match = re_blame.match(raw)
+ if match is not None:
+ blame = match.groups()[0:2]
+ raw = match.groups()[2]
+ else:
+ blame = None
+
+ if raw.startswith("package"):
+ pkg = Package(line, raw, blame)
+ elif raw.startswith(" ") and raw.endswith("{"):
+ # When provided with class callback, we treat as incremental
+ # parse and don't build up entire API
+ if clazz and clazz_cb:
+ clazz_cb(clazz)
+ clazz = Class(pkg, line, raw, blame)
+ if not clazz_cb:
api[clazz.fullname] = clazz
- elif raw.startswith(" ctor"):
- clazz.ctors.append(Method(clazz, raw, blame))
- elif raw.startswith(" method"):
- clazz.methods.append(Method(clazz, raw, blame))
- elif raw.startswith(" field"):
- clazz.fields.append(Field(clazz, raw, blame))
+ elif raw.startswith(" ctor"):
+ clazz.ctors.append(Method(clazz, line, raw, blame))
+ elif raw.startswith(" method"):
+ clazz.methods.append(Method(clazz, line, raw, blame))
+ elif raw.startswith(" field"):
+ clazz.fields.append(Field(clazz, line, raw, blame))
+
+ # Handle last trailing class
+ if clazz and clazz_cb:
+ clazz_cb(clazz)
return api
+class Failure():
+ def __init__(self, sig, clazz, detail, error, rule, msg):
+ self.sig = sig
+ self.error = error
+ self.rule = rule
+ self.msg = msg
+
+ if error:
+ self.head = "Error %s" % (rule) if rule else "Error"
+ dump = "%s%s:%s %s" % (format(fg=RED, bg=BLACK, bold=True), self.head, format(reset=True), msg)
+ else:
+ self.head = "Warning %s" % (rule) if rule else "Warning"
+ dump = "%s%s:%s %s" % (format(fg=YELLOW, bg=BLACK, bold=True), self.head, format(reset=True), msg)
+
+ self.line = clazz.line
+ blame = clazz.blame
+ if detail is not None:
+ dump += "\n in " + repr(detail)
+ self.line = detail.line
+ blame = detail.blame
+ dump += "\n in " + repr(clazz)
+ dump += "\n in " + repr(clazz.pkg)
+ dump += "\n at line " + repr(self.line)
+ if blame is not None:
+ dump += "\n last modified by %s in %s" % (blame[1], blame[0])
+
+ self.dump = dump
+
+ def __repr__(self):
+ return self.dump
+
+
failures = {}
-def _fail(clazz, detail, msg):
+def _fail(clazz, detail, error, rule, msg):
"""Records an API failure to be processed later."""
global failures
sig = "%s-%s-%s" % (clazz.fullname, repr(detail), msg)
sig = sig.replace(" deprecated ", " ")
- res = msg
- blame = clazz.blame
- if detail is not None:
- res += "\n in " + repr(detail)
- blame = detail.blame
- res += "\n in " + repr(clazz)
- res += "\n in " + repr(clazz.pkg)
- if blame is not None:
- res += "\n last modified by %s in %s" % (blame[1], blame[0])
- failures[sig] = res
+ failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
+
-def warn(clazz, detail, msg):
- _fail(clazz, detail, "%sWarning:%s %s" % (format(fg=YELLOW, bg=BLACK, bold=True), format(reset=True), msg))
+def warn(clazz, detail, rule, msg):
+ _fail(clazz, detail, False, rule, msg)
-def error(clazz, detail, msg):
- _fail(clazz, detail, "%sError:%s %s" % (format(fg=RED, bg=BLACK, bold=True), format(reset=True), msg))
+def error(clazz, detail, rule, msg):
+ _fail(clazz, detail, True, rule, msg)
def verify_constants(clazz):
@@ -218,13 +263,13 @@ def verify_constants(clazz):
for f in clazz.fields:
if "static" in f.split and "final" in f.split:
if re.match("[A-Z0-9_]+", f.name) is None:
- error(clazz, f, "Constant field names should be FOO_NAME")
+ error(clazz, f, "C2", "Constant field names must be FOO_NAME")
def verify_enums(clazz):
"""Enums are bad, mmkay?"""
if "extends java.lang.Enum" in clazz.raw:
- error(clazz, None, "Enums are not allowed")
+ error(clazz, None, "F5", "Enums are not allowed")
def verify_class_names(clazz):
@@ -234,9 +279,9 @@ def verify_class_names(clazz):
if re.match("android\.R\.[a-z]+", clazz.fullname): return
if re.search("[A-Z]{2,}", clazz.name) is not None:
- warn(clazz, None, "Class name style should be Mtp not MTP")
+ warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
if re.match("[^A-Z]", clazz.name):
- error(clazz, None, "Class must start with uppercase char")
+ error(clazz, None, "S1", "Class must start with uppercase char")
def verify_method_names(clazz):
@@ -247,9 +292,9 @@ def verify_method_names(clazz):
for m in clazz.methods:
if re.search("[A-Z]{2,}", m.name) is not None:
- warn(clazz, m, "Method name style should be getMtu() instead of getMTU()")
+ warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
if re.match("[^a-z]", m.name):
- error(clazz, m, "Method name must start with lowercase char")
+ error(clazz, m, "S1", "Method name must start with lowercase char")
def verify_callbacks(clazz):
@@ -259,17 +304,17 @@ def verify_callbacks(clazz):
if clazz.fullname == "android.speech.tts.SynthesisCallback": return
if clazz.name.endswith("Callbacks"):
- error(clazz, None, "Class name must not be plural")
+ error(clazz, None, "L1", "Callback class names should be singular")
if clazz.name.endswith("Observer"):
- warn(clazz, None, "Class should be named FooCallback")
+ warn(clazz, None, "L1", "Class should be named FooCallback")
if clazz.name.endswith("Callback"):
if "interface" in clazz.split:
- error(clazz, None, "Callback must be abstract class to enable extension in future API levels")
+ error(clazz, None, "CL3", "Callbacks must be abstract class to enable extension in future API levels")
for m in clazz.methods:
if not re.match("on[A-Z][a-z]*", m.name):
- error(clazz, m, "Callback method names must be onFoo() style")
+ error(clazz, m, "L1", "Callback method names must be onFoo() style")
def verify_listeners(clazz):
@@ -281,16 +326,16 @@ def verify_listeners(clazz):
if clazz.name.endswith("Listener"):
if " abstract class " in clazz.raw:
- error(clazz, None, "Listener should be an interface, otherwise renamed Callback")
+ error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
for m in clazz.methods:
if not re.match("on[A-Z][a-z]*", m.name):
- error(clazz, m, "Listener method names must be onFoo() style")
+ error(clazz, m, "L1", "Listener method names must be onFoo() style")
if len(clazz.methods) == 1 and clazz.name.startswith("On"):
m = clazz.methods[0]
if (m.name + "Listener").lower() != clazz.name.lower():
- error(clazz, m, "Single listener method name should match class name")
+ error(clazz, m, "L1", "Single listener method name must match class name")
def verify_actions(clazz):
@@ -308,7 +353,7 @@ def verify_actions(clazz):
if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
if not f.name.startswith("ACTION_"):
- error(clazz, f, "Intent action constant name must be ACTION_FOO")
+ error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
else:
if clazz.fullname == "android.content.Intent":
prefix = "android.intent.action"
@@ -320,7 +365,7 @@ def verify_actions(clazz):
prefix = clazz.pkg.name + ".action"
expected = prefix + "." + f.name[7:]
if f.value != expected:
- error(clazz, f, "Inconsistent action value; expected %s" % (expected))
+ error(clazz, f, "C4", "Inconsistent action value; expected %s" % (expected))
def verify_extras(clazz):
@@ -340,7 +385,7 @@ def verify_extras(clazz):
if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
if not f.name.startswith("EXTRA_"):
- error(clazz, f, "Intent extra must be EXTRA_FOO")
+ error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
else:
if clazz.pkg.name == "android.content" and clazz.name == "Intent":
prefix = "android.intent.extra"
@@ -350,7 +395,7 @@ def verify_extras(clazz):
prefix = clazz.pkg.name + ".extra"
expected = prefix + "." + f.name[6:]
if f.value != expected:
- error(clazz, f, "Inconsistent extra value; expected %s" % (expected))
+ error(clazz, f, "C4", "Inconsistent extra value; expected %s" % (expected))
def verify_equals(clazz):
@@ -359,7 +404,7 @@ def verify_equals(clazz):
eq = "equals" in methods
hc = "hashCode" in methods
if eq != hc:
- error(clazz, None, "Must override both equals and hashCode; missing one")
+ error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
def verify_parcelable(clazz):
@@ -370,17 +415,17 @@ def verify_parcelable(clazz):
describe = [ i for i in clazz.methods if i.name == "describeContents" ]
if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
- error(clazz, None, "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
+ error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
def verify_protected(clazz):
- """Verify that no protected methods are allowed."""
+ """Verify that no protected methods or fields are allowed."""
for m in clazz.methods:
if "protected" in m.split:
- error(clazz, m, "No protected methods; must be public")
+ error(clazz, m, "M7", "Protected methods not allowed; must be public")
for f in clazz.fields:
if "protected" in f.split:
- error(clazz, f, "No protected fields; must be public")
+ error(clazz, f, "M7", "Protected fields not allowed; must be public")
def verify_fields(clazz):
@@ -410,18 +455,18 @@ def verify_fields(clazz):
elif clazz.fullname.startswith("android.util.Mutable"):
pass
else:
- error(clazz, f, "Bare fields must be marked final; consider adding accessors")
+ error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
if not "static" in f.split:
if not re.match("[a-z]([a-zA-Z]+)?", f.name):
- error(clazz, f, "Non-static fields must be named with myField style")
+ error(clazz, f, "S1", "Non-static fields must be named using myField style")
if re.match("[ms][A-Z]", f.name):
- error(clazz, f, "Don't expose your internal objects")
+ error(clazz, f, "F1", "Internal objects must not be exposed")
if re.match("[A-Z_]+", f.name):
if "static" not in f.split or "final" not in f.split:
- error(clazz, f, "Constants must be marked static final")
+ error(clazz, f, "C2", "Constants must be marked static final")
def verify_register(clazz):
@@ -434,34 +479,34 @@ def verify_register(clazz):
if m.name.startswith("register"):
other = "unregister" + m.name[8:]
if other not in methods:
- error(clazz, m, "Missing unregister method")
+ error(clazz, m, "L2", "Missing unregister method")
if m.name.startswith("unregister"):
other = "register" + m.name[10:]
if other not in methods:
- error(clazz, m, "Missing register method")
+ error(clazz, m, "L2", "Missing register method")
if m.name.startswith("add") or m.name.startswith("remove"):
- error(clazz, m, "Callback methods should be named register/unregister")
+ error(clazz, m, "L3", "Callback methods should be named register/unregister")
if "Listener" in m.raw:
if m.name.startswith("add"):
other = "remove" + m.name[3:]
if other not in methods:
- error(clazz, m, "Missing remove method")
+ error(clazz, m, "L2", "Missing remove method")
if m.name.startswith("remove") and not m.name.startswith("removeAll"):
other = "add" + m.name[6:]
if other not in methods:
- error(clazz, m, "Missing add method")
+ error(clazz, m, "L2", "Missing add method")
if m.name.startswith("register") or m.name.startswith("unregister"):
- error(clazz, m, "Listener methods should be named add/remove")
+ error(clazz, m, "L3", "Listener methods should be named add/remove")
def verify_sync(clazz):
"""Verify synchronized methods aren't exposed."""
for m in clazz.methods:
if "synchronized" in m.split:
- error(clazz, m, "Internal lock exposed")
+ error(clazz, m, "M5", "Internal locks must not be exposed")
def verify_intent_builder(clazz):
@@ -473,7 +518,7 @@ def verify_intent_builder(clazz):
if m.name.startswith("create") and m.name.endswith("Intent"):
pass
else:
- error(clazz, m, "Methods creating an Intent should be named createFooIntent()")
+ warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
def verify_helper_classes(clazz):
@@ -483,57 +528,45 @@ def verify_helper_classes(clazz):
if "extends android.app.Service" in clazz.raw:
test_methods = True
if not clazz.name.endswith("Service"):
- error(clazz, None, "Inconsistent class name; should be FooService")
+ error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
found = False
for f in clazz.fields:
if f.name == "SERVICE_INTERFACE":
found = True
if f.value != clazz.fullname:
- error(clazz, f, "Inconsistent interface constant; expected %s" % (clazz.fullname))
-
- if not found:
- warn(clazz, None, "Missing SERVICE_INTERFACE constant")
-
- if "abstract" in clazz.split and not clazz.fullname.startswith("android.service."):
- warn(clazz, None, "Services extended by developers should be under android.service")
+ error(clazz, f, "C4", "Inconsistent interface constant; expected %s" % (clazz.fullname))
if "extends android.content.ContentProvider" in clazz.raw:
test_methods = True
if not clazz.name.endswith("Provider"):
- error(clazz, None, "Inconsistent class name; should be FooProvider")
+ error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
found = False
for f in clazz.fields:
if f.name == "PROVIDER_INTERFACE":
found = True
if f.value != clazz.fullname:
- error(clazz, f, "Inconsistent interface name; expected %s" % (clazz.fullname))
-
- if not found:
- warn(clazz, None, "Missing PROVIDER_INTERFACE constant")
-
- if "abstract" in clazz.split and not clazz.fullname.startswith("android.provider."):
- warn(clazz, None, "Providers extended by developers should be under android.provider")
+ error(clazz, f, "C4", "Inconsistent interface constant; expected %s" % (clazz.fullname))
if "extends android.content.BroadcastReceiver" in clazz.raw:
test_methods = True
if not clazz.name.endswith("Receiver"):
- error(clazz, None, "Inconsistent class name; should be FooReceiver")
+ error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
if "extends android.app.Activity" in clazz.raw:
test_methods = True
if not clazz.name.endswith("Activity"):
- error(clazz, None, "Inconsistent class name; should be FooActivity")
+ error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
if test_methods:
for m in clazz.methods:
if "final" in m.split: continue
if not re.match("on[A-Z]", m.name):
if "abstract" in m.split:
- error(clazz, m, "Methods implemented by developers must be named onFoo()")
+ warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
else:
- warn(clazz, m, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
+ warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
def verify_builder(clazz):
@@ -543,7 +576,7 @@ def verify_builder(clazz):
if not clazz.name.endswith("Builder"): return
if clazz.name != "Builder":
- warn(clazz, None, "Builder should be defined as inner class")
+ warn(clazz, None, None, "Builder should be defined as inner class")
has_build = False
for m in clazz.methods:
@@ -555,26 +588,26 @@ def verify_builder(clazz):
if m.name.startswith("clear"): continue
if m.name.startswith("with"):
- error(clazz, m, "Builder methods names must follow setFoo() style")
+ warn(clazz, m, None, "Builder methods names should use setFoo() style")
if m.name.startswith("set"):
if not m.typ.endswith(clazz.fullname):
- warn(clazz, m, "Methods should return the builder")
+ warn(clazz, m, "M4", "Methods must return the builder object")
if not has_build:
- warn(clazz, None, "Missing build() method")
+ warn(clazz, None, None, "Missing build() method")
def verify_aidl(clazz):
"""Catch people exposing raw AIDL."""
if "extends android.os.Binder" in clazz.raw or "implements android.os.IInterface" in clazz.raw:
- error(clazz, None, "Exposing raw AIDL interface")
+ error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
def verify_internal(clazz):
"""Catch people exposing internal classes."""
if clazz.pkg.name.startswith("com.android"):
- error(clazz, None, "Exposing internal class")
+ error(clazz, None, None, "Internal classes must not be exposed")
def verify_layering(clazz):
@@ -609,38 +642,56 @@ def verify_layering(clazz):
for f in clazz.fields:
ir = rank(f.typ)
if ir and ir < cr:
- warn(clazz, f, "Field type violates package layering")
+ warn(clazz, f, "FW6", "Field type violates package layering")
for m in clazz.methods:
ir = rank(m.typ)
if ir and ir < cr:
- warn(clazz, m, "Method return type violates package layering")
+ warn(clazz, m, "FW6", "Method return type violates package layering")
for arg in m.args:
ir = rank(arg)
if ir and ir < cr:
- warn(clazz, m, "Method argument type violates package layering")
+ warn(clazz, m, "FW6", "Method argument type violates package layering")
-def verify_boolean(clazz, api):
- """Catches people returning boolean from getFoo() style methods.
- Ignores when matching setFoo() is present."""
+def verify_boolean(clazz):
+ """Verifies that boolean accessors are named correctly.
+ For example, hasFoo() and setHasFoo()."""
- methods = [ m.name for m in clazz.methods ]
+ def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
+ def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
+
+ gets = [ m for m in clazz.methods if is_get(m) ]
+ sets = [ m for m in clazz.methods if is_set(m) ]
- builder = clazz.fullname + ".Builder"
- builder_methods = []
- if builder in api:
- builder_methods = [ m.name for m in api[builder].methods ]
+ def error_if_exists(methods, trigger, expected, actual):
+ for m in methods:
+ if m.name == actual:
+ error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
for m in clazz.methods:
- if m.typ == "boolean" and m.name.startswith("get") and m.name != "get" and len(m.args) == 0:
- setter = "set" + m.name[3:]
- if setter in methods:
- pass
- elif builder is not None and setter in builder_methods:
- pass
- else:
- warn(clazz, m, "Methods returning boolean should be named isFoo, hasFoo, areFoo")
+ if is_get(m):
+ if re.match("is[A-Z]", m.name):
+ target = m.name[2:]
+ expected = "setIs" + target
+ error_if_exists(sets, m.name, expected, "setHas" + target)
+ elif re.match("has[A-Z]", m.name):
+ target = m.name[3:]
+ expected = "setHas" + target
+ error_if_exists(sets, m.name, expected, "setIs" + target)
+ error_if_exists(sets, m.name, expected, "set" + target)
+ elif re.match("get[A-Z]", m.name):
+ target = m.name[3:]
+ expected = "set" + target
+ error_if_exists(sets, m.name, expected, "setIs" + target)
+ error_if_exists(sets, m.name, expected, "setHas" + target)
+
+ if is_set(m):
+ if re.match("set[A-Z]", m.name):
+ target = m.name[3:]
+ expected = "get" + target
+ error_if_exists(sets, m.name, expected, "is" + target)
+ error_if_exists(sets, m.name, expected, "has" + target)
def verify_collections(clazz):
@@ -651,10 +702,10 @@ def verify_collections(clazz):
"java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
for m in clazz.methods:
if m.typ in bad:
- error(clazz, m, "Return type is concrete collection; should be interface")
+ error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
for arg in m.args:
if arg in bad:
- error(clazz, m, "Argument is concrete collection; should be interface")
+ error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
def verify_flags(clazz):
@@ -669,49 +720,291 @@ def verify_flags(clazz):
scope = f.name[0:f.name.index("FLAG_")]
if val & known[scope]:
- warn(clazz, f, "Found overlapping flag constant value")
+ warn(clazz, f, "C1", "Found overlapping flag constant value")
known[scope] |= val
-def verify_style(api):
- """Find all style issues in the given API level."""
+def verify_exception(clazz):
+ """Verifies that methods don't throw generic exceptions."""
+ for m in clazz.methods:
+ if "throws java.lang.Exception" in m.raw or "throws java.lang.Throwable" in m.raw or "throws java.lang.Error" in m.raw:
+ error(clazz, m, "S1", "Methods must not throw generic exceptions")
+
+
+def verify_google(clazz):
+ """Verifies that APIs never reference Google."""
+
+ if re.search("google", clazz.raw, re.IGNORECASE):
+ error(clazz, None, None, "Must never reference Google")
+
+ test = []
+ test.extend(clazz.ctors)
+ test.extend(clazz.fields)
+ test.extend(clazz.methods)
+
+ for t in test:
+ if re.search("google", t.raw, re.IGNORECASE):
+ error(clazz, t, None, "Must never reference Google")
+
+
+def verify_bitset(clazz):
+ """Verifies that we avoid using heavy BitSet."""
+
+ for f in clazz.fields:
+ if f.typ == "java.util.BitSet":
+ error(clazz, f, None, "Field type must not be heavy BitSet")
+
+ for m in clazz.methods:
+ if m.typ == "java.util.BitSet":
+ error(clazz, m, None, "Return type must not be heavy BitSet")
+ for arg in m.args:
+ if arg == "java.util.BitSet":
+ error(clazz, m, None, "Argument type must not be heavy BitSet")
+
+
+def verify_manager(clazz):
+ """Verifies that FooManager is only obtained from Context."""
+
+ if not clazz.name.endswith("Manager"): return
+
+ for c in clazz.ctors:
+ error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
+
+
+def verify_boxed(clazz):
+ """Verifies that methods avoid boxed primitives."""
+
+ boxed = ["java.lang.Number","java.lang.Byte","java.lang.Double","java.lang.Float","java.lang.Integer","java.lang.Long","java.lang.Short"]
+
+ for c in clazz.ctors:
+ for arg in c.args:
+ if arg in boxed:
+ error(clazz, c, "M11", "Must avoid boxed primitives")
+
+ for f in clazz.fields:
+ if f.typ in boxed:
+ error(clazz, f, "M11", "Must avoid boxed primitives")
+
+ for m in clazz.methods:
+ if m.typ in boxed:
+ error(clazz, m, "M11", "Must avoid boxed primitives")
+ for arg in m.args:
+ if arg in boxed:
+ error(clazz, m, "M11", "Must avoid boxed primitives")
+
+
+def verify_static_utils(clazz):
+ """Verifies that helper classes can't be constructed."""
+ if clazz.fullname.startswith("android.opengl"): return
+ if re.match("android\.R\.[a-z]+", clazz.fullname): return
+
+ if len(clazz.fields) > 0: return
+ if len(clazz.methods) == 0: return
+
+ for m in clazz.methods:
+ if "static" not in m.split:
+ return
+
+ # At this point, we have no fields, and all methods are static
+ if len(clazz.ctors) > 0:
+ error(clazz, None, None, "Fully-static utility classes must not have constructor")
+
+
+def verify_overload_args(clazz):
+ """Verifies that method overloads add new arguments at the end."""
+ if clazz.fullname.startswith("android.opengl"): return
+
+ overloads = collections.defaultdict(list)
+ for m in clazz.methods:
+ if "deprecated" in m.split: continue
+ overloads[m.name].append(m)
+
+ for name, methods in overloads.items():
+ if len(methods) <= 1: continue
+
+ # Look for arguments common across all overloads
+ def cluster(args):
+ count = collections.defaultdict(int)
+ res = set()
+ for i in range(len(args)):
+ a = args[i]
+ res.add("%s#%d" % (a, count[a]))
+ count[a] += 1
+ return res
+
+ common_args = cluster(methods[0].args)
+ for m in methods:
+ common_args = common_args & cluster(m.args)
+
+ if len(common_args) == 0: continue
+
+ # Require that all common arguments are present at start of signature
+ locked_sig = None
+ for m in methods:
+ sig = m.args[0:len(common_args)]
+ if not common_args.issubset(cluster(sig)):
+ warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
+ elif not locked_sig:
+ locked_sig = sig
+ elif locked_sig != sig:
+ error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
+
+
+def verify_callback_handlers(clazz):
+ """Verifies that methods adding listener/callback have overload
+ for specifying delivery thread."""
+
+ # Ignore UI packages which assume main thread
+ skip = [
+ "animation",
+ "view",
+ "graphics",
+ "transition",
+ "widget",
+ "webkit",
+ ]
+ for s in skip:
+ if s in clazz.pkg.name_path: return
+ if s in clazz.extends_path: return
+
+ # Ignore UI classes which assume main thread
+ if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
+ for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
+ if s in clazz.fullname: return
+ if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
+ for s in ["Loader"]:
+ if s in clazz.fullname: return
+
+ found = {}
+ by_name = collections.defaultdict(list)
+ for m in clazz.methods:
+ if m.name.startswith("unregister"): continue
+ if m.name.startswith("remove"): continue
+ if re.match("on[A-Z]+", m.name): continue
+
+ by_name[m.name].append(m)
+
+ for a in m.args:
+ if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
+ found[m.name] = m
+
+ for f in found.values():
+ takes_handler = False
+ for m in by_name[f.name]:
+ if "android.os.Handler" in m.args:
+ takes_handler = True
+ if not takes_handler:
+ warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Handler")
+
+
+def verify_context_first(clazz):
+ """Verifies that methods accepting a Context keep it the first argument."""
+ examine = clazz.ctors + clazz.methods
+ for m in examine:
+ if len(m.args) > 1 and m.args[0] != "android.content.Context":
+ if "android.content.Context" in m.args[1:]:
+ error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
+
+
+def verify_listener_last(clazz):
+ """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
+ examine = clazz.ctors + clazz.methods
+ for m in examine:
+ if "Listener" in m.name or "Callback" in m.name: continue
+ found = False
+ for a in m.args:
+ if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
+ found = True
+ elif found and a != "android.os.Handler":
+ warn(clazz, m, "M3", "Listeners should always be at end of argument list")
+
+
+def verify_resource_names(clazz):
+ """Verifies that resource names have consistent case."""
+ if not re.match("android\.R\.[a-z]+", clazz.fullname): return
+
+ # Resources defined by files are foo_bar_baz
+ if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
+ for f in clazz.fields:
+ if re.match("[a-z1-9_]+$", f.name): continue
+ error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
+
+ # Resources defined inside files are fooBarBaz
+ if clazz.name in ["array","attr","id","bool","fraction","integer"]:
+ for f in clazz.fields:
+ if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
+ if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
+ if re.match("state_[a-z_]*$", f.name): continue
+
+ if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
+ error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
+
+ # Styles are FooBar_Baz
+ if clazz.name in ["style"]:
+ for f in clazz.fields:
+ if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
+ error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
+
+
+def examine_clazz(clazz):
+ """Find all style issues in the given class."""
+ if clazz.pkg.name.startswith("java"): return
+ if clazz.pkg.name.startswith("junit"): return
+ if clazz.pkg.name.startswith("org.apache"): return
+ if clazz.pkg.name.startswith("org.xml"): return
+ if clazz.pkg.name.startswith("org.json"): return
+ if clazz.pkg.name.startswith("org.w3c"): return
+
+ verify_constants(clazz)
+ verify_enums(clazz)
+ verify_class_names(clazz)
+ verify_method_names(clazz)
+ verify_callbacks(clazz)
+ verify_listeners(clazz)
+ verify_actions(clazz)
+ verify_extras(clazz)
+ verify_equals(clazz)
+ verify_parcelable(clazz)
+ verify_protected(clazz)
+ verify_fields(clazz)
+ verify_register(clazz)
+ verify_sync(clazz)
+ verify_intent_builder(clazz)
+ verify_helper_classes(clazz)
+ verify_builder(clazz)
+ verify_aidl(clazz)
+ verify_internal(clazz)
+ verify_layering(clazz)
+ verify_boolean(clazz)
+ verify_collections(clazz)
+ verify_flags(clazz)
+ verify_exception(clazz)
+ if not ALLOW_GOOGLE: verify_google(clazz)
+ verify_bitset(clazz)
+ verify_manager(clazz)
+ verify_boxed(clazz)
+ verify_static_utils(clazz)
+ verify_overload_args(clazz)
+ verify_callback_handlers(clazz)
+ verify_context_first(clazz)
+ verify_listener_last(clazz)
+ verify_resource_names(clazz)
+
+
+def examine_stream(stream):
+ """Find all style issues in the given API stream."""
global failures
+ failures = {}
+ _parse_stream(stream, examine_clazz)
+ return failures
+
+def examine_api(api):
+ """Find all style issues in the given parsed API."""
+ global failures
failures = {}
for key in sorted(api.keys()):
- clazz = api[key]
-
- if clazz.pkg.name.startswith("java"): continue
- if clazz.pkg.name.startswith("junit"): continue
- if clazz.pkg.name.startswith("org.apache"): continue
- if clazz.pkg.name.startswith("org.xml"): continue
- if clazz.pkg.name.startswith("org.json"): continue
- if clazz.pkg.name.startswith("org.w3c"): continue
-
- verify_constants(clazz)
- verify_enums(clazz)
- verify_class_names(clazz)
- verify_method_names(clazz)
- verify_callbacks(clazz)
- verify_listeners(clazz)
- verify_actions(clazz)
- verify_extras(clazz)
- verify_equals(clazz)
- verify_parcelable(clazz)
- verify_protected(clazz)
- verify_fields(clazz)
- verify_register(clazz)
- verify_sync(clazz)
- verify_intent_builder(clazz)
- verify_helper_classes(clazz)
- verify_builder(clazz)
- verify_aidl(clazz)
- verify_internal(clazz)
- verify_layering(clazz)
- verify_boolean(clazz, api)
- verify_collections(clazz)
- verify_flags(clazz)
-
+ examine_clazz(api[key])
return failures
@@ -749,49 +1042,71 @@ def verify_compat(cur, prev):
prev_clazz = prev[key]
if not class_exists(cur, prev_clazz):
- error(prev_clazz, None, "Class removed or incompatible change")
+ error(prev_clazz, None, None, "Class removed or incompatible change")
continue
cur_clazz = cur[key]
for test in prev_clazz.ctors:
if not ctor_exists(cur, cur_clazz, test):
- error(prev_clazz, prev_ctor, "Constructor removed or incompatible change")
+ error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
methods = all_methods(prev, prev_clazz)
for test in methods:
if not method_exists(cur, cur_clazz, test):
- error(prev_clazz, test, "Method removed or incompatible change")
+ error(prev_clazz, test, None, "Method removed or incompatible change")
for test in prev_clazz.fields:
if not field_exists(cur, cur_clazz, test):
- error(prev_clazz, test, "Field removed or incompatible change")
+ error(prev_clazz, test, None, "Field removed or incompatible change")
return failures
-cur = parse_api(sys.argv[1])
-cur_fail = verify_style(cur)
-
-if len(sys.argv) > 2:
- prev = parse_api(sys.argv[2])
- prev_fail = verify_style(prev)
-
- # ignore errors from previous API level
- for p in prev_fail:
- if p in cur_fail:
- del cur_fail[p]
-
- # look for compatibility issues
- compat_fail = verify_compat(cur, prev)
-
- print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
- for f in sorted(compat_fail):
- print compat_fail[f]
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Enforces common Android public API design \
+ patterns. It ignores lint messages from a previous API level, if provided.")
+ parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
+ parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
+ help="previous.txt")
+ parser.add_argument("--no-color", action='store_const', const=True,
+ help="Disable terminal colors")
+ parser.add_argument("--allow-google", action='store_const', const=True,
+ help="Allow references to Google")
+ args = vars(parser.parse_args())
+
+ if args['no_color']:
+ USE_COLOR = False
+
+ if args['allow_google']:
+ ALLOW_GOOGLE = True
+
+ current_file = args['current.txt']
+ previous_file = args['previous.txt']
+
+ with current_file as f:
+ cur_fail = examine_stream(f)
+ if not previous_file is None:
+ with previous_file as f:
+ prev_fail = examine_stream(f)
+
+ # ignore errors from previous API level
+ for p in prev_fail:
+ if p in cur_fail:
+ del cur_fail[p]
+
+ """
+ # NOTE: disabled because of memory pressure
+ # look for compatibility issues
+ compat_fail = verify_compat(cur, prev)
+
+ print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
+ for f in sorted(compat_fail):
+ print compat_fail[f]
+ print
+ """
+
+ print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
+ for f in sorted(cur_fail):
+ print cur_fail[f]
print
-
-
-print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
-for f in sorted(cur_fail):
- print cur_fail[f]
- print
diff --git a/tools/layoutlib/.idea/libraries/asm_4_0.xml b/tools/layoutlib/.idea/libraries/asm_4_0.xml
deleted file mode 100644
index 7df287f..0000000
--- a/tools/layoutlib/.idea/libraries/asm_4_0.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="asm-4.0">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/asm/asm-4.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/asm/src.zip!/" />
- </SOURCES>
- </library>
-</component> \ No newline at end of file
diff --git a/tools/layoutlib/.idea/libraries/guava.xml b/tools/layoutlib/.idea/libraries/guava.xml
deleted file mode 100644
index eb60719..0000000
--- a/tools/layoutlib/.idea/libraries/guava.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="guava">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../../../prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../../../prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0-sources.jar!/" />
- </SOURCES>
- </library>
-</component> \ No newline at end of file
diff --git a/tools/layoutlib/.idea/libraries/icu4j.xml b/tools/layoutlib/.idea/libraries/icu4j.xml
deleted file mode 100644
index dbe0bd7..0000000
--- a/tools/layoutlib/.idea/libraries/icu4j.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="icu4j">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/icu4j/icu4j.jar!/" />
- </CLASSES>
- <JAVADOC>
- <root url="http://icu-project.org/apiref/icu4j50rc/" />
- </JAVADOC>
- <SOURCES />
- </library>
-</component> \ No newline at end of file
diff --git a/tools/layoutlib/.idea/libraries/kxml2_2_3_0.xml b/tools/layoutlib/.idea/libraries/kxml2_2_3_0.xml
deleted file mode 100644
index 2a65050..0000000
--- a/tools/layoutlib/.idea/libraries/kxml2_2_3_0.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="kxml2-2.3.0">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/kxml2/kxml2-2.3.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="file://$PROJECT_DIR$/../../../../libcore/xml/src/main/java" />
- </SOURCES>
- </library>
-</component> \ No newline at end of file
diff --git a/tools/layoutlib/.idea/libraries/ninepatch_prebuilt.xml b/tools/layoutlib/.idea/libraries/ninepatch_prebuilt.xml
deleted file mode 100644
index f34f7dd..0000000
--- a/tools/layoutlib/.idea/libraries/ninepatch_prebuilt.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="ninepatch-prebuilt">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/ninepatch/ninepatch-prebuilt.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="file://$ANDROID_SRC$/tools/base/ninepatch/src/main/java" />
- </SOURCES>
- </library>
-</component> \ No newline at end of file
diff --git a/tools/layoutlib/.idea/libraries/tools_common_prebuilt.xml b/tools/layoutlib/.idea/libraries/tools_common_prebuilt.xml
deleted file mode 100644
index b325ad4..0000000
--- a/tools/layoutlib/.idea/libraries/tools_common_prebuilt.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<component name="libraryTable">
- <library name="tools-common-prebuilt">
- <ANNOTATIONS>
- <root url="file://$PROJECT_DIR$" />
- </ANNOTATIONS>
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/tools-common/tools-common-prebuilt.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="file://$ANDROID_SRC$/tools/base/common/src/main/java" />
- </SOURCES>
- </library>
-</component> \ No newline at end of file
diff --git a/tools/layoutlib/.idea/misc.xml b/tools/layoutlib/.idea/misc.xml
index 94bcd36..b474bdc 100644
--- a/tools/layoutlib/.idea/misc.xml
+++ b/tools/layoutlib/.idea/misc.xml
@@ -10,27 +10,29 @@
<type id="android" />
</component>
<component name="NullableNotNullManager">
- <option name="myDefaultNullable" value="com.android.annotations.Nullable" />
- <option name="myDefaultNotNull" value="com.android.annotations.NonNull" />
+ <option name="myDefaultNullable" value="android.annotation.Nullable" />
+ <option name="myDefaultNotNull" value="android.annotation.NonNull" />
<option name="myNullables">
<value>
- <list size="5">
+ <list size="6">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
<item index="4" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
+ <item index="5" class="java.lang.String" itemvalue="android.annotation.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
- <list size="5">
+ <list size="6">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
<item index="4" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
+ <item index="5" class="java.lang.String" itemvalue="android.annotation.NonNull" />
</list>
</value>
</option>
diff --git a/tools/layoutlib/.idea/runConfigurations/Create.xml b/tools/layoutlib/.idea/runConfigurations/Create.xml
index ff173e5..58f057a 100644
--- a/tools/layoutlib/.idea/runConfigurations/Create.xml
+++ b/tools/layoutlib/.idea/runConfigurations/Create.xml
@@ -3,7 +3,7 @@
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="com.android.tools.layoutlib.create.Main" />
<option name="VM_PARAMETERS" value="" />
- <option name="PROGRAM_PARAMETERS" value="out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar out/target/common/obj/JAVA_LIBRARIES/core-libart_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/javalib.jar" />
+ <option name="PROGRAM_PARAMETERS" value="out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar out/target/common/obj/JAVA_LIBRARIES/core-libart_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/icu4j-icudata-jarjar_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/icu4j-icutzdata-jarjar_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/javalib.jar" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/../../../../" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" value="" />
diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk
index 9300401..61ddb04 100644
--- a/tools/layoutlib/Android.mk
+++ b/tools/layoutlib/Android.mk
@@ -37,6 +37,10 @@ built_ext_dep := $(call java-lib-deps,ext)
built_ext_classes := $(call java-lib-files,ext)
built_ext_data := $(call intermediates-dir-for, \
JAVA_LIBRARIES,ext,,COMMON)/javalib.jar
+built_icudata_dep := $(call java-lib-deps,icu4j-icudata-jarjar)
+built_icudata_data := $(call java-lib-files,icu4j-icudata-jarjar)
+built_icutzdata_dep := $(call java-lib-deps,icu4j-icutzdata-jarjar)
+built_icutzdata_data := $(call java-lib-files,icu4j-icutzdata-jarjar)
built_layoutlib_create_jar := $(call intermediates-dir-for, \
JAVA_LIBRARIES,layoutlib_create,HOST)/javalib.jar
@@ -56,6 +60,8 @@ $(LOCAL_BUILT_MODULE): $(built_core_dep) \
$(built_framework_dep) \
$(built_ext_dep) \
$(built_ext_data) \
+ $(built_icudata_dep) \
+ $(built_icutzdata_dep) \
$(built_layoutlib_create_jar)
$(hide) echo "host layoutlib_create: $@"
$(hide) mkdir -p $(dir $@)
@@ -66,6 +72,8 @@ $(LOCAL_BUILT_MODULE): $(built_core_dep) \
$(built_core_classes) \
$(built_framework_classes) \
$(built_ext_classes) \
+ $(built_icudata_data) \
+ $(built_icutzdata_data) \
$(built_ext_data)
$(hide) ls -l $(built_framework_classes)
diff --git a/tools/layoutlib/bridge/Android.mk b/tools/layoutlib/bridge/Android.mk
index cfd597e..0dbdd56 100644
--- a/tools/layoutlib/bridge/Android.mk
+++ b/tools/layoutlib/bridge/Android.mk
@@ -22,8 +22,6 @@ LOCAL_JAVACFLAGS := -source 6 -target 6
LOCAL_JAVA_LIBRARIES := \
- kxml2-2.3.0 \
- icu4j \
layoutlib_api-prebuilt \
tools-common-prebuilt
diff --git a/tools/layoutlib/bridge/bridge.iml b/tools/layoutlib/bridge/bridge.iml
index 0f96916..af2fe7f 100644
--- a/tools/layoutlib/bridge/bridge.iml
+++ b/tools/layoutlib/bridge/bridge.iml
@@ -24,15 +24,57 @@
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="library" name="icu4j" level="project" />
- <orderEntry type="library" name="kxml2-2.3.0" level="project" />
<orderEntry type="library" name="layoutlib_api-prebuilt" level="project" />
- <orderEntry type="library" name="ninepatch-prebuilt" level="project" />
- <orderEntry type="library" name="tools-common-prebuilt" level="project" />
+ <orderEntry type="module-library">
+ <library name="ninepatch-prebuilt">
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/ninepatch/ninepatch-prebuilt.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="file://$ANDROID_SRC$/tools/base/ninepatch/src/main/java" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library">
+ <library name="tools-common-prebuilt">
+ <ANNOTATIONS>
+ <root url="file://$MODULE_DIR$/.." />
+ </ANNOTATIONS>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/tools-common/tools-common-prebuilt.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="file://$ANDROID_SRC$/tools/base/common/src/main/java" />
+ </SOURCES>
+ </library>
+ </orderEntry>
<orderEntry type="library" name="framework.jar" level="project" />
- <orderEntry type="library" scope="TEST" name="guava" level="project" />
<orderEntry type="module-library" scope="TEST">
- <library>
+ <library name="kxml2-2.3.0">
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/kxml2/kxml2-2.3.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="file://$MODULE_DIR$/../../../../../libcore/xml/src/main/java" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" scope="TEST">
+ <library name="guava">
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../../prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../../prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" scope="TEST">
+ <library name="sdk-common">
<CLASSES>
<root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/sdk-common/sdk-common.jar!/" />
</CLASSES>
@@ -44,5 +86,4 @@
</orderEntry>
<orderEntry type="library" scope="TEST" name="JUnit4" level="application" />
</component>
-</module>
-
+</module> \ No newline at end of file
diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_100.png
deleted file mode 100644
index f17189a..0000000
--- a/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_100.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_100.png
deleted file mode 100644
index 2a9757d..0000000
--- a/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_100.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/navigation_bar.xml b/tools/layoutlib/bridge/resources/bars/navigation_bar.xml
index 599ca08..55bd1d2 100644
--- a/tools/layoutlib/bridge/resources/bars/navigation_bar.xml
+++ b/tools/layoutlib/bridge/resources/bars/navigation_bar.xml
@@ -1,20 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <TextView
+ <View
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_weight="1"/>
+ android:visibility="invisible"/>
<ImageView
android:layout_height="wrap_content"
- android:layout_width="wrap_content"/>
+ android:layout_width="wrap_content"
+ android:scaleType="centerInside"/>
+ <View
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_weight="1"
+ android:visibility="invisible"/>
<ImageView
android:layout_height="wrap_content"
- android:layout_width="wrap_content"/>
+ android:layout_width="wrap_content"
+ android:scaleType="centerInside"/>
+ <View
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_weight="1"
+ android:visibility="invisible"/>
<ImageView
android:layout_height="wrap_content"
- android:layout_width="wrap_content"/>
- <TextView
+ android:layout_width="wrap_content"
+ android:scaleType="centerInside"/>
+ <View
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_weight="1"/>
+ android:visibility="invisible"/>
</merge>
diff --git a/tools/layoutlib/bridge/resources/bars/navigation_bar600dp.xml b/tools/layoutlib/bridge/resources/bars/navigation_bar600dp.xml
new file mode 100644
index 0000000..e208a0d
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/navigation_bar600dp.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <View
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:visibility="invisible"/>
+ <ImageView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:scaleType="centerInside"/>
+ <View
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:visibility="invisible"/>
+ <ImageView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:scaleType="centerInside"/>
+ <View
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:visibility="invisible"/>
+ <ImageView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:scaleType="centerInside"/>
+ <View
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:visibility="invisible"/>
+</merge>
diff --git a/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_back.png
index b28624f..d2760bb 100644
--- a/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_back.png
+++ b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_back.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_home.png
index 3f3e288..df43e21 100644
--- a/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_home.png
+++ b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_home.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_recent.png
index 06dcd20..6fab1d6 100644
--- a/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_recent.png
+++ b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_recent.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-hdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-hdpi/ic_sysbar_back.png
index e464347..2fcfdde 100644
--- a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-hdpi/ic_sysbar_back.png
+++ b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-hdpi/ic_sysbar_back.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-mdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-mdpi/ic_sysbar_back.png
index 1b578a6..48708a5 100644
--- a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-mdpi/ic_sysbar_back.png
+++ b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-mdpi/ic_sysbar_back.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xhdpi/ic_sysbar_back.png
index 373e84a..3d73184 100644
--- a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xhdpi/ic_sysbar_back.png
+++ b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xhdpi/ic_sysbar_back.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xxhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xxhdpi/ic_sysbar_back.png
index 6b19593..786935d 100644
--- a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xxhdpi/ic_sysbar_back.png
+++ b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xxhdpi/ic_sysbar_back.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_back.png
index f878093..1d8c3af 100644
--- a/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_back.png
+++ b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_back.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_home.png
index 8e9583b..66de0ec 100644
--- a/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_home.png
+++ b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_home.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_recent.png
index e2a89c3..30c65f5 100644
--- a/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_recent.png
+++ b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_recent.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_back.png
index ec2951d..a356285 100644
--- a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_back.png
+++ b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_back.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_home.png
index 254f757..ba2d0b2 100644
--- a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_home.png
+++ b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_home.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_recent.png
index 8a8e941..94a74b1 100644
--- a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_recent.png
+++ b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_recent.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_back.png
index 77969b8..29da099 100644
--- a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_back.png
+++ b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_back.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_home.png
index d60229f..59b32f2 100644
--- a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_home.png
+++ b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_home.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_recent.png
index a261f85..ba66d27 100644
--- a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_recent.png
+++ b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_recent.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_100.png
deleted file mode 100644
index 555bcd9..0000000
--- a/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_100.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
index 2c2c672..163fbcb 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
@@ -16,6 +16,8 @@
package android.content.res;
+import com.android.SdkConstants;
+import com.android.ide.common.rendering.api.ArrayResourceValue;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.LayoutlibCallback;
import com.android.ide.common.rendering.api.ResourceValue;
@@ -32,6 +34,8 @@ import com.android.util.Pair;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -42,6 +46,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
+import java.util.Iterator;
/**
*
@@ -169,7 +174,7 @@ public final class BridgeResources extends Resources {
}
@Override
- public int getColor(int id) throws NotFoundException {
+ public int getColor(int id, Theme theme) throws NotFoundException {
Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
if (value != null) {
@@ -192,22 +197,21 @@ public final class BridgeResources extends Resources {
}
}
- // id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
-
- // this is not used since the method above always throws
- return 0;
+ // Suppress possible NPE. getColorStateList will never return null, it will instead
+ // throw an exception, but intelliJ can't figure that out
+ //noinspection ConstantConditions
+ return getColorStateList(id, theme).getDefaultColor();
}
@Override
- public ColorStateList getColorStateList(int id) throws NotFoundException {
+ public ColorStateList getColorStateList(int id, Theme theme) throws NotFoundException {
Pair<String, ResourceValue> resValue = getResourceValue(id, mPlatformResourceFlag);
if (resValue != null) {
ColorStateList stateList = ResourceHelper.getColorStateList(resValue.getSecond(),
mContext);
if (stateList != null) {
- return stateList;
+ return stateList.obtainForTheme(theme);
}
}
@@ -242,6 +246,145 @@ public final class BridgeResources extends Resources {
}
@Override
+ public CharSequence[] getTextArray(int id) throws NotFoundException {
+ ResourceValue resValue = getArrayResourceValue(id);
+ if (resValue == null) {
+ // Error already logged by getArrayResourceValue.
+ return new CharSequence[0];
+ } else if (!(resValue instanceof ArrayResourceValue)) {
+ return new CharSequence[]{
+ resolveReference(resValue.getValue(), resValue.isFramework())};
+ }
+ ArrayResourceValue arv = ((ArrayResourceValue) resValue);
+ return fillValues(arv, new CharSequence[arv.getElementCount()]);
+ }
+
+ @Override
+ public String[] getStringArray(int id) throws NotFoundException {
+ ResourceValue resValue = getArrayResourceValue(id);
+ if (resValue == null) {
+ // Error already logged by getArrayResourceValue.
+ return new String[0];
+ } else if (!(resValue instanceof ArrayResourceValue)) {
+ return new String[]{
+ resolveReference(resValue.getValue(), resValue.isFramework())};
+ }
+ ArrayResourceValue arv = ((ArrayResourceValue) resValue);
+ return fillValues(arv, new String[arv.getElementCount()]);
+ }
+
+ /**
+ * Resolve each element in resValue and copy them to {@code values}. The values copied are
+ * always Strings. The ideal signature for the method should be &lt;T super String&gt;, but java
+ * generics don't support it.
+ */
+ private <T extends CharSequence> T[] fillValues(ArrayResourceValue resValue, T[] values) {
+ int i = 0;
+ for (Iterator<String> iterator = resValue.iterator(); iterator.hasNext(); i++) {
+ @SuppressWarnings("unchecked")
+ T s = (T) resolveReference(iterator.next(), resValue.isFramework());
+ values[i] = s;
+ }
+ return values;
+ }
+
+ @Override
+ public int[] getIntArray(int id) throws NotFoundException {
+ ResourceValue rv = getArrayResourceValue(id);
+ if (rv == null) {
+ // Error already logged by getArrayResourceValue.
+ return new int[0];
+ } else if (!(rv instanceof ArrayResourceValue)) {
+ // This is an older IDE that can only give us the first element of the array.
+ String firstValue = resolveReference(rv.getValue(), rv.isFramework());
+ try {
+ return new int[]{getInt(firstValue)};
+ } catch (NumberFormatException e) {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
+ "Integer resource array contains non-integer value: " +
+ firstValue, null);
+ return new int[1];
+ }
+ }
+ ArrayResourceValue resValue = ((ArrayResourceValue) rv);
+ int[] values = new int[resValue.getElementCount()];
+ int i = 0;
+ for (Iterator<String> iterator = resValue.iterator(); iterator.hasNext(); i++) {
+ String element = resolveReference(iterator.next(), resValue.isFramework());
+ try {
+ values[i] = getInt(element);
+ } catch (NumberFormatException e) {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
+ "Integer resource array contains non-integer value: " + element, null);
+ }
+ }
+ return values;
+ }
+
+ /**
+ * Try to find the ArrayResourceValue for the given id.
+ * <p/>
+ * If the ResourceValue found is not of type {@link ResourceType#ARRAY}, the method logs an
+ * error and return null. However, if the ResourceValue found has type {@code
+ * ResourceType.ARRAY}, but the value is not an instance of {@link ArrayResourceValue}, the
+ * method returns the ResourceValue. This happens on older versions of the IDE, which did not
+ * parse the array resources properly.
+ * <p/>
+ * @throws NotFoundException if no resource if found
+ */
+ @Nullable
+ private ResourceValue getArrayResourceValue(int id) throws NotFoundException {
+ Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag);
+
+ if (v != null) {
+ ResourceValue resValue = v.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ final ResourceType type = resValue.getResourceType();
+ if (type != ResourceType.ARRAY) {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE,
+ String.format(
+ "Resource with id 0x%1$X is not an array resource, but %2$s",
+ id, type == null ? "null" : type.getDisplayName()),
+ null);
+ return null;
+ }
+ if (!(resValue instanceof ArrayResourceValue)) {
+ Bridge.getLog().warning(LayoutLog.TAG_UNSUPPORTED,
+ "Obtaining resource arrays via getTextArray, getStringArray or getIntArray is not fully supported in this version of the IDE.",
+ null);
+ }
+ return resValue;
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @NonNull
+ private String resolveReference(@NonNull String ref, boolean forceFrameworkOnly) {
+ if (ref.startsWith(SdkConstants.PREFIX_RESOURCE_REF) || ref.startsWith
+ (SdkConstants.PREFIX_THEME_REF)) {
+ ResourceValue rv =
+ mContext.getRenderResources().findResValue(ref, forceFrameworkOnly);
+ rv = mContext.getRenderResources().resolveResValue(rv);
+ if (rv != null) {
+ return rv.getValue();
+ } else {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE,
+ "Unable to resolve resource " + ref, null);
+ }
+ }
+ // Not a reference.
+ return ref;
+ }
+
+ @Override
public XmlResourceParser getLayout(int id) throws NotFoundException {
Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag);
@@ -431,13 +574,8 @@ public final class BridgeResources extends Resources {
if (resValue != null) {
String v = resValue.getValue();
if (v != null) {
- int radix = 10;
- if (v.startsWith("0x")) {
- v = v.substring(2);
- radix = 16;
- }
try {
- return Integer.parseInt(v, radix);
+ return getInt(v);
} catch (NumberFormatException e) {
// return exception below
}
@@ -610,7 +748,6 @@ public final class BridgeResources extends Resources {
}
}
-
@Override
public InputStream openRawResource(int id) throws NotFoundException {
Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
@@ -691,7 +828,7 @@ public final class BridgeResources extends Resources {
resourceInfo = mLayoutlibCallback.resolveResourceId(id);
}
- String message = null;
+ String message;
if (resourceInfo != null) {
message = String.format(
"Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.",
@@ -703,4 +840,15 @@ public final class BridgeResources extends Resources {
throw new NotFoundException(message);
}
+
+ private int getInt(String v) throws NumberFormatException {
+ int radix = 10;
+ if (v.startsWith("0x")) {
+ v = v.substring(2);
+ radix = 16;
+ } else if (v.startsWith("0")) {
+ radix = 8;
+ }
+ return Integer.parseInt(v, radix);
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
index 7d4271b..2e515fb 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
@@ -16,7 +16,6 @@
package android.content.res;
-import com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.AttrResourceValue;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.RenderResources;
@@ -33,6 +32,7 @@ import com.android.resources.ResourceType;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.Nullable;
import android.content.res.Resources.Theme;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
@@ -45,7 +45,24 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
-import static com.android.ide.common.rendering.api.RenderResources.*;
+import static android.util.TypedValue.TYPE_ATTRIBUTE;
+import static android.util.TypedValue.TYPE_DIMENSION;
+import static android.util.TypedValue.TYPE_FLOAT;
+import static android.util.TypedValue.TYPE_INT_BOOLEAN;
+import static android.util.TypedValue.TYPE_INT_COLOR_ARGB4;
+import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8;
+import static android.util.TypedValue.TYPE_INT_COLOR_RGB4;
+import static android.util.TypedValue.TYPE_INT_COLOR_RGB8;
+import static android.util.TypedValue.TYPE_INT_DEC;
+import static android.util.TypedValue.TYPE_INT_HEX;
+import static android.util.TypedValue.TYPE_NULL;
+import static android.util.TypedValue.TYPE_REFERENCE;
+import static android.util.TypedValue.TYPE_STRING;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.PREFIX_THEME_REF;
+import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_EMPTY;
+import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_NULL;
+import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_UNDEFINED;
/**
* Custom implementation of TypedArray to handle non compiled resources.
@@ -223,7 +240,7 @@ public final class BridgeTypedArray extends TypedArray {
String s = getString(index);
try {
if (s != null) {
- return XmlUtils.convertValueToInt(s, defValue);
+ return convertValueToInt(s, defValue);
}
} catch (NumberFormatException e) {
Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
@@ -320,7 +337,8 @@ public final class BridgeTypedArray extends TypedArray {
BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
parser, mContext, resValue.isFramework());
try {
- return ColorStateList.createFromXml(mContext.getResources(), blockParser);
+ return ColorStateList.createFromXml(mContext.getResources(), blockParser,
+ mContext.getTheme());
} finally {
blockParser.ensurePopped();
}
@@ -444,7 +462,7 @@ public final class BridgeTypedArray extends TypedArray {
@Override
public int getDimensionPixelSize(int index, int defValue) {
try {
- return getDimension(index);
+ return getDimension(index, null);
} catch (RuntimeException e) {
String s = getString(index);
@@ -474,12 +492,12 @@ public final class BridgeTypedArray extends TypedArray {
@Override
public int getLayoutDimension(int index, String name) {
try {
- // this will throw an exception
- return getDimension(index);
+ // this will throw an exception if not found.
+ return getDimension(index, name);
} catch (RuntimeException e) {
if (LayoutInflater_Delegate.sIsInInclude) {
- throw new RuntimeException();
+ throw new RuntimeException("Layout Dimension '" + name + "' not found.");
}
Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
@@ -494,9 +512,13 @@ public final class BridgeTypedArray extends TypedArray {
return getDimensionPixelSize(index, defValue);
}
- private int getDimension(int index) {
+ /** @param name attribute name, used for error reporting. */
+ private int getDimension(int index, @Nullable String name) {
String s = getString(index);
if (s == null) {
+ if (name != null) {
+ throw new RuntimeException("Attribute '" + name + "' not found");
+ }
throw new RuntimeException();
}
// Check if the value is a magic constant that doesn't require a unit.
@@ -758,6 +780,60 @@ public final class BridgeTypedArray extends TypedArray {
return s != null && ResourceHelper.parseFloatAttribute(mNames[index], s, outValue, false);
}
+ @Override
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public int getType(int index) {
+ String value = getString(index);
+ if (value == null) {
+ return TYPE_NULL;
+ }
+ if (value.startsWith(PREFIX_RESOURCE_REF)) {
+ return TYPE_REFERENCE;
+ }
+ if (value.startsWith(PREFIX_THEME_REF)) {
+ return TYPE_ATTRIBUTE;
+ }
+ try {
+ // Don't care about the value. Only called to check if an exception is thrown.
+ convertValueToInt(value, 0);
+ if (value.startsWith("0x") || value.startsWith("0X")) {
+ return TYPE_INT_HEX;
+ }
+ // is it a color?
+ if (value.startsWith("#")) {
+ int length = value.length() - 1;
+ if (length == 3) { // rgb
+ return TYPE_INT_COLOR_RGB4;
+ }
+ if (length == 4) { // argb
+ return TYPE_INT_COLOR_ARGB4;
+ }
+ if (length == 6) { // rrggbb
+ return TYPE_INT_COLOR_RGB8;
+ }
+ if (length == 8) { // aarrggbb
+ return TYPE_INT_COLOR_ARGB8;
+ }
+ }
+ if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
+ return TYPE_INT_BOOLEAN;
+ }
+ return TYPE_INT_DEC;
+ } catch (NumberFormatException ignored) {
+ try {
+ Float.parseFloat(value);
+ return TYPE_FLOAT;
+ } catch (NumberFormatException ignore) {
+ }
+ // Might be a dimension.
+ if (ResourceHelper.parseFloatAttribute(null, value, new TypedValue(), false)) {
+ return TYPE_DIMENSION;
+ }
+ }
+ // TODO: handle fractions.
+ return TYPE_STRING;
+ }
+
/**
* Determines whether there is an attribute at <var>index</var>.
*
@@ -867,6 +943,52 @@ public final class BridgeTypedArray extends TypedArray {
return null;
}
+ /**
+ * Copied from {@link XmlUtils#convertValueToInt(CharSequence, int)}, but adapted to account
+ * for aapt, and the fact that host Java VM's Integer.parseInt("XXXXXXXX", 16) cannot handle
+ * "XXXXXXXX" > 80000000.
+ */
+ private static int convertValueToInt(@Nullable String charSeq, int defValue) {
+ if (null == charSeq)
+ return defValue;
+
+ int sign = 1;
+ int index = 0;
+ int len = charSeq.length();
+ int base = 10;
+
+ if ('-' == charSeq.charAt(0)) {
+ sign = -1;
+ index++;
+ }
+
+ if ('0' == charSeq.charAt(index)) {
+ // Quick check for a zero by itself
+ if (index == (len - 1))
+ return 0;
+
+ char c = charSeq.charAt(index + 1);
+
+ if ('x' == c || 'X' == c) {
+ index += 2;
+ base = 16;
+ } else {
+ index++;
+ // Leave the base as 10. aapt removes the preceding zero, and thus when framework
+ // sees the value, it only gets the decimal value.
+ }
+ } else if ('#' == charSeq.charAt(index)) {
+ return ResourceHelper.getColor(charSeq) * sign;
+ } else if ("true".equals(charSeq) || "TRUE".equals(charSeq)) {
+ return -1;
+ } else if ("false".equals(charSeq) || "FALSE".equals(charSeq)) {
+ return 0;
+ }
+
+ // Use Long, since we want to handle hex ints > 80000000.
+ return ((int)Long.parseLong(charSeq.substring(index), base)) * sign;
+ }
+
static TypedArray obtain(Resources res, int len) {
return res instanceof BridgeResources ?
new BridgeTypedArray(((BridgeResources) res), null, len, true) : null;
diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java
index 4bd83e9..f1e8fc2 100644
--- a/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java
@@ -16,7 +16,6 @@
package android.content.res;
-import com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.StyleResourceValue;
import com.android.layoutlib.bridge.android.BridgeContext;
@@ -25,8 +24,10 @@ import com.android.layoutlib.bridge.impl.RenderSessionImpl;
import com.android.resources.ResourceType;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import android.annotation.Nullable;
import android.content.res.Resources.NotFoundException;
import android.content.res.Resources.Theme;
+import android.content.res.Resources.ThemeKey;
import android.util.AttributeSet;
import android.util.TypedValue;
@@ -110,22 +111,16 @@ public class Resources_Theme_Delegate {
private static boolean setupResources(Theme thisTheme) {
// Key is a space-separated list of theme ids applied that have been merged into the
// BridgeContext's theme to make thisTheme.
- String[] appliedStyles = thisTheme.getKey().split(" ");
+ final ThemeKey key = thisTheme.getKey();
+ final int[] resId = key.mResId;
+ final boolean[] force = key.mForce;
+
boolean changed = false;
- for (String s : appliedStyles) {
- if (s.isEmpty()) {
- continue;
- }
- // See the definition of force parameter in Theme.applyStyle().
- boolean force = false;
- if (s.charAt(s.length() - 1) == '!') {
- force = true;
- s = s.substring(0, s.length() - 1);
- }
- int styleId = Integer.parseInt(s, 16);
- StyleResourceValue style = resolveStyle(styleId);
+ for (int i = 0, N = key.mCount; i < N; i++) {
+ StyleResourceValue style = resolveStyle(resId[i]);
if (style != null) {
- RenderSessionImpl.getCurrentContext().getRenderResources().applyStyle(style, force);
+ RenderSessionImpl.getCurrentContext().getRenderResources().applyStyle(
+ style, force[i]);
changed = true;
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
index a4a3b7d..21f36ce 100644
--- a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
+++ b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
@@ -19,6 +19,12 @@ package android.graphics;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.layoutlib.bridge.Bridge;
+import android.graphics.Paint_Delegate.FontInfo;
+import android.icu.lang.UScript;
+import android.icu.lang.UScriptRun;
+import android.icu.text.Bidi;
+import android.icu.text.BidiRun;
+
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Toolkit;
@@ -29,13 +35,6 @@ import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
-import com.ibm.icu.lang.UScript;
-import com.ibm.icu.lang.UScriptRun;
-import com.ibm.icu.text.Bidi;
-import com.ibm.icu.text.BidiRun;
-
-import android.graphics.Paint_Delegate.FontInfo;
-
/**
* Render the text by breaking it into various scripts and using the right font for each script.
* Can be used to measure the text without actually drawing it.
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
index 9cf777d..d858953 100644
--- a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
@@ -16,13 +16,13 @@
package android.graphics;
-import com.android.annotations.Nullable;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.ninepatch.NinePatchChunk;
import com.android.resources.Density;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import android.annotation.Nullable;
import android.content.res.BridgeResources.NinePatchInputStream;
import android.graphics.BitmapFactory.Options;
import android.graphics.Bitmap_Delegate.BitmapCreateFlags;
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java
index e9b5d6e..af47aeb 100644
--- a/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java
@@ -23,7 +23,15 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.graphics.Shader.TileMode;
+import java.awt.PaintContext;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
+import java.awt.image.Raster;
/**
* Delegate implementing the native methods of android.graphics.BitmapShader
@@ -67,9 +75,9 @@ public class BitmapShader_Delegate extends Shader_Delegate {
// ---- native methods ----
@LayoutlibDelegate
- /*package*/ static long nativeCreate(long native_bitmap, int shaderTileModeX,
+ /*package*/ static long nativeCreate(Bitmap androidBitmap, int shaderTileModeX,
int shaderTileModeY) {
- Bitmap_Delegate bitmap = Bitmap_Delegate.getDelegate(native_bitmap);
+ Bitmap_Delegate bitmap = Bitmap_Delegate.getDelegate(androidBitmap);
if (bitmap == null) {
return 0;
}
@@ -83,17 +91,17 @@ public class BitmapShader_Delegate extends Shader_Delegate {
// ---- Private delegate/helper methods ----
- private BitmapShader_Delegate(java.awt.image.BufferedImage image,
+ private BitmapShader_Delegate(BufferedImage image,
TileMode tileModeX, TileMode tileModeY) {
mJavaPaint = new BitmapShaderPaint(image, tileModeX, tileModeY);
}
private class BitmapShaderPaint implements java.awt.Paint {
- private final java.awt.image.BufferedImage mImage;
+ private final BufferedImage mImage;
private final TileMode mTileModeX;
private final TileMode mTileModeY;
- BitmapShaderPaint(java.awt.image.BufferedImage image,
+ BitmapShaderPaint(BufferedImage image,
TileMode tileModeX, TileMode tileModeY) {
mImage = image;
mTileModeX = tileModeX;
@@ -101,29 +109,24 @@ public class BitmapShader_Delegate extends Shader_Delegate {
}
@Override
- public java.awt.PaintContext createContext(
- java.awt.image.ColorModel colorModel,
- java.awt.Rectangle deviceBounds,
- java.awt.geom.Rectangle2D userBounds,
- java.awt.geom.AffineTransform xform,
- java.awt.RenderingHints hints) {
-
- java.awt.geom.AffineTransform canvasMatrix;
+ public PaintContext createContext(ColorModel colorModel, Rectangle deviceBounds,
+ Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) {
+ AffineTransform canvasMatrix;
try {
canvasMatrix = xform.createInverse();
- } catch (java.awt.geom.NoninvertibleTransformException e) {
+ } catch (NoninvertibleTransformException e) {
Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
"Unable to inverse matrix in BitmapShader", e, null /*data*/);
- canvasMatrix = new java.awt.geom.AffineTransform();
+ canvasMatrix = new AffineTransform();
}
- java.awt.geom.AffineTransform localMatrix = getLocalMatrix();
+ AffineTransform localMatrix = getLocalMatrix();
try {
localMatrix = localMatrix.createInverse();
- } catch (java.awt.geom.NoninvertibleTransformException e) {
+ } catch (NoninvertibleTransformException e) {
Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
"Unable to inverse matrix in BitmapShader", e, null /*data*/);
- localMatrix = new java.awt.geom.AffineTransform();
+ localMatrix = new AffineTransform();
}
if (!colorModel.isCompatibleRaster(mImage.getRaster())) {
@@ -134,16 +137,16 @@ public class BitmapShader_Delegate extends Shader_Delegate {
return new BitmapShaderContext(canvasMatrix, localMatrix, colorModel);
}
- private class BitmapShaderContext implements java.awt.PaintContext {
+ private class BitmapShaderContext implements PaintContext {
- private final java.awt.geom.AffineTransform mCanvasMatrix;
- private final java.awt.geom.AffineTransform mLocalMatrix;
- private final java.awt.image.ColorModel mColorModel;
+ private final AffineTransform mCanvasMatrix;
+ private final AffineTransform mLocalMatrix;
+ private final ColorModel mColorModel;
public BitmapShaderContext(
- java.awt.geom.AffineTransform canvasMatrix,
- java.awt.geom.AffineTransform localMatrix,
- java.awt.image.ColorModel colorModel) {
+ AffineTransform canvasMatrix,
+ AffineTransform localMatrix,
+ ColorModel colorModel) {
mCanvasMatrix = canvasMatrix;
mLocalMatrix = localMatrix;
mColorModel = colorModel;
@@ -154,13 +157,13 @@ public class BitmapShader_Delegate extends Shader_Delegate {
}
@Override
- public java.awt.image.ColorModel getColorModel() {
+ public ColorModel getColorModel() {
return mColorModel;
}
@Override
- public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
- java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(
+ public Raster getRaster(int x, int y, int w, int h) {
+ BufferedImage image = new BufferedImage(
mColorModel, mColorModel.createCompatibleWritableRaster(w, h),
mColorModel.isAlphaPremultiplied(), null);
diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
index 8d24d38..0737682 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -22,6 +22,7 @@ import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.resources.Density;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import android.annotation.Nullable;
import android.graphics.Bitmap.Config;
import android.os.Parcel;
@@ -76,19 +77,18 @@ public final class Bitmap_Delegate {
// ---- Public Helper methods ----
/**
- * Returns the native delegate associated to a given {@link Bitmap_Delegate} object.
- */
- public static Bitmap_Delegate getDelegate(Bitmap bitmap) {
- return sManager.getDelegate(bitmap.mNativeBitmap);
- }
-
- /**
* Returns the native delegate associated to a given an int referencing a {@link Bitmap} object.
*/
public static Bitmap_Delegate getDelegate(long native_bitmap) {
return sManager.getDelegate(native_bitmap);
}
+ @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());
+ }
+
/**
* Creates and returns a {@link Bitmap} initialized with the given file content.
*
@@ -187,31 +187,7 @@ public final class Bitmap_Delegate {
return createBitmap(delegate, createFlags, density.getDpiValue());
}
- /**
- * Returns the {@link BufferedImage} used by the delegate of the given {@link Bitmap}.
- */
- public static BufferedImage getImage(Bitmap bitmap) {
- // get the delegate from the native int.
- Bitmap_Delegate delegate = sManager.getDelegate(bitmap.mNativeBitmap);
- if (delegate == null) {
- return null;
- }
-
- return delegate.mImage;
- }
-
- public static int getBufferedImageType(int nativeBitmapConfig) {
- switch (Config.nativeToConfig(nativeBitmapConfig)) {
- case ALPHA_8:
- return BufferedImage.TYPE_INT_ARGB;
- case RGB_565:
- return BufferedImage.TYPE_INT_ARGB;
- case ARGB_4444:
- return BufferedImage.TYPE_INT_ARGB;
- case ARGB_8888:
- return BufferedImage.TYPE_INT_ARGB;
- }
-
+ private static int getBufferedImageType() {
return BufferedImage.TYPE_INT_ARGB;
}
@@ -238,10 +214,6 @@ public final class Bitmap_Delegate {
return mHasAlpha && mConfig != Config.RGB_565;
}
- public boolean hasMipMap() {
- // TODO: check if more checks are required as in hasAlpha.
- return mHasMipMap;
- }
/**
* Update the generationId.
*
@@ -256,7 +228,7 @@ public final class Bitmap_Delegate {
@LayoutlibDelegate
/*package*/ static Bitmap nativeCreate(int[] colors, int offset, int stride, int width,
int height, int nativeConfig, boolean isMutable) {
- int imageType = getBufferedImageType(nativeConfig);
+ int imageType = getBufferedImageType();
// create the image
BufferedImage image = new BufferedImage(width, height, imageType);
@@ -284,7 +256,7 @@ public final class Bitmap_Delegate {
int width = srcImage.getWidth();
int height = srcImage.getHeight();
- int imageType = getBufferedImageType(nativeConfig);
+ int imageType = getBufferedImageType();
// create the image
BufferedImage image = new BufferedImage(width, height, imageType);
@@ -302,6 +274,13 @@ public final class Bitmap_Delegate {
}
@LayoutlibDelegate
+ /*package*/ static Bitmap nativeCopyAshmem(long nativeSrcBitmap) {
+ // Unused method; no implementation provided.
+ assert false;
+ return null;
+ }
+
+ @LayoutlibDelegate
/*package*/ static void nativeDestructor(long nativeBitmap) {
sManager.removeJavaReferenceFor(nativeBitmap);
}
@@ -373,22 +352,16 @@ public final class Bitmap_Delegate {
/*package*/ static boolean nativeHasAlpha(long nativeBitmap) {
// get the delegate from the native int.
Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
- if (delegate == null) {
- return true;
- }
+ return delegate == null || delegate.mHasAlpha;
- return delegate.mHasAlpha;
}
@LayoutlibDelegate
/*package*/ static boolean nativeHasMipMap(long nativeBitmap) {
// get the delegate from the native int.
Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
- if (delegate == null) {
- return true;
- }
+ return delegate == null || delegate.mHasMipMap;
- return delegate.mHasMipMap;
}
@LayoutlibDelegate
@@ -509,11 +482,6 @@ public final class Bitmap_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nativePrepareToDraw(long nativeBitmap) {
- // nothing to be done here.
- }
-
- @LayoutlibDelegate
/*package*/ static boolean nativeIsPremultiplied(long nativeBitmap) {
// get the delegate from the native int.
Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
@@ -599,6 +567,14 @@ 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;
+ }
+
// ---- Private delegate/helper methods ----
private Bitmap_Delegate(BufferedImage image, Config config) {
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index 4d2d100..f8b3739 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -23,6 +23,7 @@ import com.android.layoutlib.bridge.impl.GcSnapshot;
import com.android.layoutlib.bridge.impl.PorterDuffUtility;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import android.annotation.Nullable;
import android.graphics.Bitmap.Config;
import android.text.TextUtils;
@@ -114,7 +115,11 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static long initRaster(long nativeBitmapOrZero) {
+ /*package*/ static long initRaster(@Nullable Bitmap bitmap) {
+ long nativeBitmapOrZero = 0;
+ if (bitmap != null) {
+ nativeBitmapOrZero = bitmap.refSkPixelRef();
+ }
if (nativeBitmapOrZero > 0) {
// get the Bitmap from the int
Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmapOrZero);
@@ -132,8 +137,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/
- static void native_setBitmap(long canvas, long bitmap, boolean copyState) {
+ /*package*/ static void native_setBitmap(long canvas, Bitmap bitmap) {
Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas);
Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
if (canvasDelegate == null || bitmapDelegate==null) {
@@ -220,7 +224,8 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_restore(long nativeCanvas) {
+ /*package*/ static void native_restore(long nativeCanvas, boolean throwOnUnderflow) {
+ // FIXME: implement throwOnUnderflow.
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
@@ -231,7 +236,9 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_restoreToCount(long nativeCanvas, int saveCount) {
+ /*package*/ static void native_restoreToCount(long nativeCanvas, int saveCount,
+ boolean throwOnUnderflow) {
+ // FIXME: implement throwOnUnderflow.
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
@@ -424,8 +431,7 @@ public final class Canvas_Delegate {
canvasDelegate.mDrawFilter = DrawFilter_Delegate.getDelegate(nativeFilter);
- if (canvasDelegate.mDrawFilter != null &&
- canvasDelegate.mDrawFilter.isSupported() == false) {
+ if (canvasDelegate.mDrawFilter != null && !canvasDelegate.mDrawFilter.isSupported()) {
Bridge.getLog().fidelityWarning(LayoutLog.TAG_DRAWFILTER,
canvasDelegate.mDrawFilter.getSupportMessage(), null, null /*data*/);
}
@@ -441,7 +447,7 @@ public final class Canvas_Delegate {
}
Rectangle rect = canvasDelegate.getSnapshot().getClip().getBounds();
- if (rect != null && rect.isEmpty() == false) {
+ if (rect != null && !rect.isEmpty()) {
bounds.left = rect.x;
bounds.top = rect.y;
bounds.right = rect.x + rect.width;
@@ -717,7 +723,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_drawBitmap(Canvas thisCanvas, long nativeCanvas, long bitmap,
+ /*package*/ static void native_drawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap,
float left, float top,
long nativePaintOrZero,
int canvasDensity,
@@ -739,7 +745,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void native_drawBitmap(Canvas thisCanvas, long nativeCanvas, long bitmap,
+ /*package*/ 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) {
@@ -780,7 +786,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nativeDrawBitmapMatrix(long nCanvas, long nBitmap,
+ /*package*/ static void nativeDrawBitmapMatrix(long nCanvas, Bitmap bitmap,
long nMatrix, long nPaint) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
@@ -792,7 +798,7 @@ public final class Canvas_Delegate {
Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
// get the delegate from the native int.
- Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nBitmap);
+ Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
if (bitmapDelegate == null) {
return;
}
@@ -821,7 +827,7 @@ public final class Canvas_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nativeDrawBitmapMesh(long nCanvas, long nBitmap,
+ /*package*/ static void nativeDrawBitmapMesh(long nCanvas, Bitmap bitmap,
int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors,
int colorOffset, long nPaint) {
// FIXME
@@ -844,7 +850,7 @@ public final class Canvas_Delegate {
@LayoutlibDelegate
/*package*/ 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 == Canvas.DIRECTION_RTL,
+ drawText(nativeCanvas, text, index, count, startX, startY, (flags & 1) != 0,
paint, typeface);
}
@@ -1038,8 +1044,7 @@ public final class Canvas_Delegate {
}
/**
- * Restores the {@link GcSnapshot} to <var>saveCount</var>
- * @param saveCount the saveCount
+ * Restores the top {@link GcSnapshot}
*/
private void restore() {
mSnapshot = mSnapshot.restore();
@@ -1102,7 +1107,7 @@ public final class Canvas_Delegate {
// before drawing it.
if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) {
fixAlpha8Bitmap(image);
- } else if (bitmap.hasAlpha() == false) {
+ } 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:
@@ -1122,7 +1127,7 @@ public final class Canvas_Delegate {
}
// if we can't force SRC mode, then create a temp bitmap of TYPE_RGB
- if (forceSrcMode[0] == false) {
+ if (!forceSrcMode[0]) {
image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF);
}
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
index 42de4ec..857e6d0 100644
--- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
@@ -16,14 +16,14 @@
package android.graphics;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.AssetRepository;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.res.AssetManager;
import android.content.res.BridgeAssetManager;
diff --git a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
index e16dbda..e8d34d0 100644
--- a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
@@ -90,7 +90,7 @@ public final class NinePatch_Delegate {
if (oos != null) {
try {
oos.close();
- } catch (IOException e) {
+ } catch (IOException ignored) {
}
}
}
@@ -136,7 +136,7 @@ public final class NinePatch_Delegate {
if (ois != null) {
try {
ois.close();
- } catch (IOException e) {
+ } catch (IOException ignored) {
}
}
}
@@ -150,15 +150,12 @@ public final class NinePatch_Delegate {
@LayoutlibDelegate
/*package*/ static boolean isNinePatchChunk(byte[] chunk) {
NinePatchChunk chunkObject = getChunk(chunk);
- if (chunkObject != null) {
- return true;
- }
+ return chunkObject != null;
- return false;
}
@LayoutlibDelegate
- /*package*/ static long validateNinePatchChunk(long bitmap, byte[] chunk) {
+ /*package*/ static long validateNinePatchChunk(byte[] chunk) {
// the default JNI implementation only checks that the byte[] has the same
// size as the C struct it represent. Since we cannot do the same check (serialization
// will return different size depending on content), we do nothing.
@@ -173,7 +170,7 @@ public final class NinePatch_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nativeDraw(long canvas_instance, RectF loc, long bitmap_instance,
+ /*package*/ static void nativeDraw(long canvas_instance, RectF loc, Bitmap bitmap_instance,
long chunk, long paint_instance_or_null, int destDensity, int srcDensity) {
draw(canvas_instance,
(int) loc.left, (int) loc.top, (int) loc.right, (int) loc.bottom,
@@ -182,7 +179,7 @@ public final class NinePatch_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nativeDraw(long canvas_instance, Rect loc, long bitmap_instance,
+ /*package*/ static void nativeDraw(long canvas_instance, Rect loc, Bitmap bitmap_instance,
long chunk, long paint_instance_or_null, int destDensity, int srcDensity) {
draw(canvas_instance,
loc.left, loc.top, loc.right, loc.bottom,
@@ -191,7 +188,7 @@ public final class NinePatch_Delegate {
}
@LayoutlibDelegate
- /*package*/ static long nativeGetTransparentRegion(long bitmap, long chunk, Rect location) {
+ /*package*/ static long nativeGetTransparentRegion(Bitmap bitmap, long chunk, Rect location) {
return 0;
}
@@ -199,7 +196,7 @@ public final class NinePatch_Delegate {
private static void draw(long canvas_instance,
final int left, final int top, final int right, final int bottom,
- long bitmap_instance, long chunk, long paint_instance_or_null,
+ Bitmap bitmap_instance, long chunk, long paint_instance_or_null,
final int destDensity, final int srcDensity) {
// get the delegate from the native int.
final Bitmap_Delegate bitmap_delegate = Bitmap_Delegate.getDelegate(bitmap_instance);
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
index 7b07404..57dd429 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
@@ -21,6 +21,8 @@ import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.FontFamily_Delegate.FontVariant;
import android.graphics.Paint.FontMetrics;
import android.graphics.Paint.FontMetricsInt;
@@ -83,6 +85,8 @@ public class Paint_Delegate {
private float mTextScaleX;
private float mTextSkewX;
private int mHintingMode = Paint.HINTING_ON;
+ private int mHyphenEdit;
+ private float mLetterSpacing; // 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;
@@ -100,6 +104,7 @@ public class Paint_Delegate {
// ---- Public Helper methods ----
+ @Nullable
public static Paint_Delegate getDelegate(long native_paint) {
return sManager.getDelegate(native_paint);
}
@@ -251,7 +256,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static int getFlags(Paint thisPaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return 0;
}
@@ -264,7 +269,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static void setFlags(Paint thisPaint, int flags) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return;
}
@@ -280,7 +285,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static int getHinting(Paint thisPaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return Paint.HINTING_ON;
}
@@ -291,7 +296,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static void setHinting(Paint thisPaint, int mode) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return;
}
@@ -337,7 +342,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static int getColor(Paint thisPaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return 0;
}
@@ -348,7 +353,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static void setColor(Paint thisPaint, int color) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return;
}
@@ -359,7 +364,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static int getAlpha(Paint thisPaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return 0;
}
@@ -370,7 +375,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static void setAlpha(Paint thisPaint, int a) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return;
}
@@ -381,7 +386,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static float getStrokeWidth(Paint thisPaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return 1.f;
}
@@ -392,7 +397,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static void setStrokeWidth(Paint thisPaint, float width) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return;
}
@@ -403,7 +408,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static float getStrokeMiter(Paint thisPaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return 1.f;
}
@@ -414,7 +419,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static void setStrokeMiter(Paint thisPaint, float miter) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return;
}
@@ -441,14 +446,14 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static boolean isElegantTextHeight(Paint thisPaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
return delegate != null && delegate.mFontVariant == FontVariant.ELEGANT;
}
@LayoutlibDelegate
/*package*/ static void setElegantTextHeight(Paint thisPaint, boolean elegant) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return;
}
@@ -459,7 +464,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static float getTextSize(Paint thisPaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return 1.f;
}
@@ -470,7 +475,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static void setTextSize(Paint thisPaint, float textSize) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return;
}
@@ -482,7 +487,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static float getTextScaleX(Paint thisPaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return 1.f;
}
@@ -493,7 +498,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static void setTextScaleX(Paint thisPaint, float scaleX) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return;
}
@@ -505,7 +510,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static float getTextSkewX(Paint thisPaint) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return 1.f;
}
@@ -516,7 +521,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static void setTextSkewX(Paint thisPaint, float skewX) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return;
}
@@ -528,7 +533,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static float ascent(Paint thisPaint) {
// get the delegate
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return 0;
}
@@ -545,7 +550,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static float descent(Paint thisPaint) {
// get the delegate
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return 0;
}
@@ -562,7 +567,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static float getFontMetrics(Paint thisPaint, FontMetrics metrics) {
// get the delegate
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return 0;
}
@@ -573,7 +578,7 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static int getFontMetricsInt(Paint thisPaint, FontMetricsInt fmi) {
// get the delegate
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return 0;
}
@@ -599,7 +604,7 @@ public class Paint_Delegate {
/*package*/ static float native_measureText(Paint thisPaint, char[] text, int index,
int count, int bidiFlags) {
// get the delegate
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return 0;
}
@@ -1088,18 +1093,107 @@ public class Paint_Delegate {
@LayoutlibDelegate
/*package*/ static float native_getLetterSpacing(long nativePaint) {
- // TODO: throw a fidelity warning.
- return 0;
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+ if (delegate == null) {
+ return 0;
+ }
+ return delegate.mLetterSpacing;
}
@LayoutlibDelegate
/*package*/ static void native_setLetterSpacing(long nativePaint, float letterSpacing) {
- // pass.
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING,
+ "Paint.setLetterSpacing() not supported.", null, null);
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+ if (delegate == null) {
+ return;
+ }
+ delegate.mLetterSpacing = letterSpacing;
}
@LayoutlibDelegate
/*package*/ static void native_setFontFeatureSettings(long nativePaint, String settings) {
- // pass.
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING,
+ "Paint.setFontFeatureSettings() not supported.", null, null);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_getHyphenEdit(long nativePaint) {
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+ if (delegate == null) {
+ return 0;
+ }
+ return delegate.mHyphenEdit;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_setHyphenEdit(long nativePaint, int hyphen) {
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+ if (delegate == null) {
+ return;
+ }
+ delegate.mHyphenEdit = hyphen;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_hasGlyph(long nativePaint, long nativeTypeface, int bidiFlags,
+ String string) {
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+ if (delegate == null) {
+ return false;
+ }
+ if (string.length() == 0) {
+ return false;
+ }
+ if (string.length() > 1) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING,
+ "Paint.hasGlyph() is not supported for ligatures.", null, null);
+ return false;
+ }
+ assert nativeTypeface == delegate.mNativeTypeface;
+ Typeface_Delegate typeface_delegate = Typeface_Delegate.getDelegate(nativeTypeface);
+
+ char c = string.charAt(0);
+ for (Font font : typeface_delegate.getFonts(delegate.mFontVariant)) {
+ if (font.canDisplay(c)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ @LayoutlibDelegate
+ /*package*/ static float native_getRunAdvance(long nativePaint, long nativeTypeface,
+ @NonNull char[] text, int start, int end, int contextStart, int contextEnd,
+ boolean isRtl, int offset) {
+ int count = end - start;
+ float[] advances = new float[count];
+ native_getTextRunAdvances(nativePaint, nativeTypeface, text, start, count,
+ contextStart, contextEnd - contextStart, isRtl, advances, 0);
+ float sum = 0;
+ for (int i = 0; i < offset; i++) {
+ sum += advances[i];
+ }
+ return sum;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_getOffsetForAdvance(long nativePaint, long nativeTypeface,
+ char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl,
+ float advance) {
+ int count = end - start;
+ float[] advances = new float[count];
+ native_getTextRunAdvances(nativePaint, nativeTypeface, text, start, count,
+ contextStart, contextEnd - contextStart, isRtl, advances, 0);
+ float sum = 0;
+ int i;
+ for (i = 0; i < count && sum < advance; i++) {
+ sum += advances[i];
+ }
+ float distanceToI = sum - advance;
+ float distanceToIMinus1 = advance - (sum - advances[i]);
+ return distanceToI > distanceToIMinus1 ? i : i - 1;
}
// ---- Private delegate/helper methods ----
@@ -1137,7 +1231,7 @@ public class Paint_Delegate {
}
private void reset() {
- mFlags = Paint.DEFAULT_PAINT_FLAGS;
+ mFlags = Paint.HIDDEN_DEFAULT_PAINT_FLAGS;
mColor = 0xFF000000;
mStyle = Paint.Style.FILL.nativeInt;
mCap = Paint.Cap.BUTT.nativeInt;
@@ -1232,7 +1326,7 @@ public class Paint_Delegate {
private static void setFlag(Paint thisPaint, int flagMask, boolean flagValue) {
// get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
if (delegate == null) {
return;
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java
index 14e9960..0d491a0 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java
@@ -81,14 +81,15 @@ public abstract class Shader_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nativeSetLocalMatrix(long native_shader, long matrix_instance) {
+ /*package*/ static long nativeSetLocalMatrix(long native_shader, long matrix_instance) {
// get the delegate from the native int.
Shader_Delegate shaderDelegate = sManager.getDelegate(native_shader);
if (shaderDelegate == null) {
- return;
+ return native_shader;
}
shaderDelegate.mLocalMatrix = Matrix_Delegate.getDelegate(matrix_instance);
+ return native_shader;
}
// ---- Private delegate/helper methods ----
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index b9460b4..85e65e6 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -16,10 +16,10 @@
package android.graphics;
-import com.android.annotations.NonNull;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import android.annotation.NonNull;
import android.graphics.FontFamily_Delegate.FontVariant;
import java.awt.Font;
diff --git a/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java b/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java
index 6247dae..38171dc 100644
--- a/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java
@@ -19,8 +19,8 @@ package android.text;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.layoutlib.bridge.Bridge;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-import com.ibm.icu.text.Bidi;
+import android.icu.text.Bidi;
/**
* Delegate used to provide new implementation for the native methods of {@link AndroidBidi}
diff --git a/tools/layoutlib/bridge/src/android/text/GreedyLineBreaker.java b/tools/layoutlib/bridge/src/android/text/GreedyLineBreaker.java
new file mode 100644
index 0000000..50289e9
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/text/GreedyLineBreaker.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.annotation.NonNull;
+import android.text.Primitive.PrimitiveType;
+import android.text.StaticLayout.LineBreaks;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.text.Primitive.PrimitiveType.PENALTY_INFINITY;
+
+// Based on the native implementation of GreedyLineBreaker in
+// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260
+public class GreedyLineBreaker extends LineBreaker {
+
+ public GreedyLineBreaker(@NonNull List<Primitive> primitives, @NonNull LineWidth lineWidth,
+ @NonNull TabStops tabStops) {
+ super(primitives, lineWidth, tabStops);
+ }
+
+ @Override
+ public void computeBreaks(@NonNull LineBreaks lineBreaks) {
+ BreakInfo breakInfo = new BreakInfo();
+ int lineNum = 0;
+ float width = 0, printedWidth = 0;
+ boolean breakFound = false, goodBreakFound = false;
+ int breakIndex = 0, goodBreakIndex = 0;
+ float breakWidth = 0, goodBreakWidth = 0;
+ int firstTabIndex = Integer.MAX_VALUE;
+
+ float maxWidth = mLineWidth.getLineWidth(lineNum);
+
+ int numPrimitives = mPrimitives.size();
+ // greedily fit as many characters as possible on each line
+ // loop over all primitives, and choose the best break point
+ // (if possible, a break point without splitting a word)
+ // after going over the maximum length
+ for (int i = 0; i < numPrimitives; i++) {
+ Primitive p = mPrimitives.get(i);
+
+ // update the current line width
+ if (p.type == PrimitiveType.BOX || p.type == PrimitiveType.GLUE) {
+ width += p.width;
+ if (p.type == PrimitiveType.BOX) {
+ printedWidth = width;
+ }
+ } else if (p.type == PrimitiveType.VARIABLE) {
+ width = mTabStops.width(width);
+ // keep track of first tab character in the region we are examining
+ // so we can determine whether or not a line contains a tab
+ firstTabIndex = Math.min(firstTabIndex, i);
+ }
+
+ // find the best break point for the characters examined so far
+ if (printedWidth > maxWidth) {
+ //noinspection StatementWithEmptyBody
+ if (breakFound || goodBreakFound) {
+ if (goodBreakFound) {
+ // a true line break opportunity existed in the characters examined so far,
+ // so there is no need to split a word
+ i = goodBreakIndex; // no +1 because of i++
+ lineNum++;
+ maxWidth = mLineWidth.getLineWidth(lineNum);
+ breakInfo.mBreaksList.add(mPrimitives.get(goodBreakIndex).location);
+ breakInfo.mWidthsList.add(goodBreakWidth);
+ breakInfo.mFlagsList.add(firstTabIndex < goodBreakIndex);
+ firstTabIndex = Integer.MAX_VALUE;
+ } else {
+ // must split a word because there is no other option
+ i = breakIndex; // no +1 because of i++
+ lineNum++;
+ maxWidth = mLineWidth.getLineWidth(lineNum);
+ breakInfo.mBreaksList.add(mPrimitives.get(breakIndex).location);
+ breakInfo.mWidthsList.add(breakWidth);
+ breakInfo.mFlagsList.add(firstTabIndex < breakIndex);
+ firstTabIndex = Integer.MAX_VALUE;
+ }
+ printedWidth = width = 0;
+ goodBreakFound = breakFound = false;
+ goodBreakWidth = breakWidth = 0;
+ continue;
+ } else {
+ // no choice, keep going... must make progress by putting at least one
+ // character on a line, even if part of that character is cut off --
+ // there is no other option
+ }
+ }
+
+ // update possible break points
+ if (p.type == PrimitiveType.PENALTY &&
+ p.penalty < PENALTY_INFINITY) {
+ // this does not handle penalties with width
+
+ // handle forced line break
+ if (p.penalty == -PENALTY_INFINITY) {
+ lineNum++;
+ maxWidth = mLineWidth.getLineWidth(lineNum);
+ breakInfo.mBreaksList.add(p.location);
+ breakInfo.mWidthsList.add(printedWidth);
+ breakInfo.mFlagsList.add(firstTabIndex < i);
+ firstTabIndex = Integer.MAX_VALUE;
+ printedWidth = width = 0;
+ goodBreakFound = breakFound = false;
+ goodBreakWidth = breakWidth = 0;
+ continue;
+ }
+ if (i > breakIndex && (printedWidth <= maxWidth || !breakFound)) {
+ breakFound = true;
+ breakIndex = i;
+ breakWidth = printedWidth;
+ }
+ if (i > goodBreakIndex && printedWidth <= maxWidth) {
+ goodBreakFound = true;
+ goodBreakIndex = i;
+ goodBreakWidth = printedWidth;
+ }
+ } else if (p.type == PrimitiveType.WORD_BREAK) {
+ // only do this if necessary -- we don't want to break words
+ // when possible, but sometimes it is unavoidable
+ if (i > breakIndex && (printedWidth <= maxWidth || !breakFound)) {
+ breakFound = true;
+ breakIndex = i;
+ breakWidth = printedWidth;
+ }
+ }
+ }
+
+ if (breakFound || goodBreakFound) {
+ // output last break if there are more characters to output
+ if (goodBreakFound) {
+ breakInfo.mBreaksList.add(mPrimitives.get(goodBreakIndex).location);
+ breakInfo.mWidthsList.add(goodBreakWidth);
+ breakInfo.mFlagsList.add(firstTabIndex < goodBreakIndex);
+ } else {
+ breakInfo.mBreaksList.add(mPrimitives.get(breakIndex).location);
+ breakInfo.mWidthsList.add(breakWidth);
+ breakInfo.mFlagsList.add(firstTabIndex < breakIndex);
+ }
+ }
+ breakInfo.copyTo(lineBreaks);
+ }
+
+ private static class BreakInfo {
+ List<Integer> mBreaksList = new ArrayList<Integer>();
+ List<Float> mWidthsList = new ArrayList<Float>();
+ List<Boolean> mFlagsList = new ArrayList<Boolean>();
+
+ public void copyTo(LineBreaks lineBreaks) {
+ if (lineBreaks.breaks.length != mBreaksList.size()) {
+ lineBreaks.breaks = new int[mBreaksList.size()];
+ lineBreaks.widths = new float[mWidthsList.size()];
+ lineBreaks.flags = new int[mFlagsList.size()];
+ }
+
+ int i = 0;
+ for (int b : mBreaksList) {
+ lineBreaks.breaks[i] = b;
+ i++;
+ }
+ i = 0;
+ for (float b : mWidthsList) {
+ lineBreaks.widths[i] = b;
+ i++;
+ }
+ i = 0;
+ for (boolean b : mFlagsList) {
+ lineBreaks.flags[i] = b ? TAB_MASK : 0;
+ i++;
+ }
+
+ mBreaksList = null;
+ mWidthsList = null;
+ mFlagsList = null;
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java b/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java
new file mode 100644
index 0000000..5a59597
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.io.File;
+
+/**
+ * Delegate that overrides implementation for certain methods in {@link android.text.StaticLayout}
+ * <p/>
+ * Through the layoutlib_create tool, selected methods of StaticLayout have been replaced
+ * by calls to methods of the same name in this delegate class.
+ */
+public class Hyphenator_Delegate {
+
+ private static final DelegateManager<Hyphenator_Delegate> sDelegateManager = new
+ DelegateManager<Hyphenator_Delegate>(Hyphenator_Delegate.class);
+
+ @LayoutlibDelegate
+ /*package*/ static File getSystemHyphenatorLocation() {
+ // FIXME
+ return null;
+ }
+
+ /*package*/ static long loadHyphenator(String patternData) {
+ return sDelegateManager.addNewDelegate(new Hyphenator_Delegate());
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/text/LineBreaker.java b/tools/layoutlib/bridge/src/android/text/LineBreaker.java
new file mode 100644
index 0000000..06e9c84
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/text/LineBreaker.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.annotation.NonNull;
+import android.text.StaticLayout.LineBreaks;
+
+import java.util.Collections;
+import java.util.List;
+
+// Based on the native implementation of LineBreaker in
+// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260
+public abstract class LineBreaker {
+
+ protected static final int TAB_MASK = 0x20000000; // keep in sync with StaticLayout
+
+ protected final @NonNull List<Primitive> mPrimitives;
+ protected final @NonNull LineWidth mLineWidth;
+ protected final @NonNull TabStops mTabStops;
+
+ public LineBreaker(@NonNull List<Primitive> primitives, @NonNull LineWidth lineWidth,
+ @NonNull TabStops tabStops) {
+ mPrimitives = Collections.unmodifiableList(primitives);
+ mLineWidth = lineWidth;
+ mTabStops = tabStops;
+ }
+
+ public abstract void computeBreaks(@NonNull LineBreaks breakInfo);
+}
diff --git a/tools/layoutlib/bridge/src/android/text/LineWidth.java b/tools/layoutlib/bridge/src/android/text/LineWidth.java
new file mode 100644
index 0000000..2ea886d
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/text/LineWidth.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+// Based on the native implementation of LineWidth in
+// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260
+public class LineWidth {
+ private final float mFirstWidth;
+ private final int mFirstWidthLineCount;
+ private float mRestWidth;
+
+ public LineWidth(float firstWidth, int firstWidthLineCount, float restWidth) {
+ mFirstWidth = firstWidth;
+ mFirstWidthLineCount = firstWidthLineCount;
+ mRestWidth = restWidth;
+ }
+
+ public float getLineWidth(int line) {
+ return (line < mFirstWidthLineCount) ? mFirstWidth : mRestWidth;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/text/OptimizingLineBreaker.java b/tools/layoutlib/bridge/src/android/text/OptimizingLineBreaker.java
new file mode 100644
index 0000000..ed8e33a
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/text/OptimizingLineBreaker.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.annotation.NonNull;
+import android.text.Primitive.PrimitiveType;
+import android.text.StaticLayout.LineBreaks;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+
+import static android.text.Primitive.PrimitiveType.PENALTY_INFINITY;
+
+
+// Based on the native implementation of OptimizingLineBreaker in
+// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260
+/**
+ * A more complex version of line breaking where we try to prevent the right edge from being too
+ * jagged.
+ */
+public class OptimizingLineBreaker extends LineBreaker {
+
+ public OptimizingLineBreaker(@NonNull List<Primitive> primitives, @NonNull LineWidth lineWidth,
+ @NonNull TabStops tabStops) {
+ super(primitives, lineWidth, tabStops);
+ }
+
+ @Override
+ public void computeBreaks(@NonNull LineBreaks breakInfo) {
+ int numBreaks = mPrimitives.size();
+ assert numBreaks > 0;
+ if (numBreaks == 1) {
+ // This can be true only if it's an empty paragraph.
+ Primitive p = mPrimitives.get(0);
+ assert p.type == PrimitiveType.PENALTY;
+ breakInfo.breaks = new int[]{0};
+ breakInfo.widths = new float[]{p.width};
+ breakInfo.flags = new int[]{0};
+ return;
+ }
+ Node[] opt = new Node[numBreaks];
+ opt[0] = new Node(-1, 0, 0, 0, false);
+ opt[numBreaks - 1] = new Node(-1, 0, 0, 0, false);
+
+ ArrayList<Integer> active = new ArrayList<Integer>();
+ active.add(0);
+ int lastBreak = 0;
+ for (int i = 0; i < numBreaks; i++) {
+ Primitive p = mPrimitives.get(i);
+ if (p.type == PrimitiveType.PENALTY) {
+ boolean finalBreak = (i + 1 == numBreaks);
+ Node bestBreak = null;
+
+ for (ListIterator<Integer> it = active.listIterator(); it.hasNext();
+ /* incrementing done in loop */) {
+ int pos = it.next();
+ int lines = opt[pos].mPrevCount;
+ float maxWidth = mLineWidth.getLineWidth(lines);
+ // we have to compute metrics every time --
+ // we can't really pre-compute this stuff and just deal with breaks
+ // because of the way tab characters work, this makes it computationally
+ // harder, but this way, we can still optimize while treating tab characters
+ // correctly
+ LineMetrics lineMetrics = computeMetrics(pos, i);
+ if (lineMetrics.mPrintedWidth <= maxWidth) {
+ float demerits = computeDemerits(maxWidth, lineMetrics.mPrintedWidth,
+ finalBreak, p.penalty) + opt[pos].mDemerits;
+ if (bestBreak == null || demerits < bestBreak.mDemerits) {
+ if (bestBreak == null) {
+ bestBreak = new Node(pos, opt[pos].mPrevCount + 1, demerits,
+ lineMetrics.mPrintedWidth, lineMetrics.mHasTabs);
+ } else {
+ bestBreak.mPrev = pos;
+ bestBreak.mPrevCount = opt[pos].mPrevCount + 1;
+ bestBreak.mDemerits = demerits;
+ bestBreak.mWidth = lineMetrics.mPrintedWidth;
+ bestBreak.mHasTabs = lineMetrics.mHasTabs;
+ }
+ }
+ } else {
+ it.remove();
+ }
+ }
+ if (p.penalty == -PENALTY_INFINITY) {
+ active.clear();
+ }
+ if (bestBreak != null) {
+ opt[i] = bestBreak;
+ active.add(i);
+ lastBreak = i;
+ }
+ if (active.isEmpty()) {
+ // we can't give up!
+ LineMetrics lineMetrics = new LineMetrics();
+ int lines = opt[lastBreak].mPrevCount;
+ float maxWidth = mLineWidth.getLineWidth(lines);
+ int breakIndex = desperateBreak(lastBreak, numBreaks, maxWidth, lineMetrics);
+ opt[breakIndex] = new Node(lastBreak, lines + 1, 0 /*doesn't matter*/,
+ lineMetrics.mWidth, lineMetrics.mHasTabs);
+ active.add(breakIndex);
+ lastBreak = breakIndex;
+ i = breakIndex; // incremented by i++
+ }
+ }
+ }
+
+ int idx = numBreaks - 1;
+ int count = opt[idx].mPrevCount;
+ resize(breakInfo, count);
+ while (opt[idx].mPrev != -1) {
+ count--;
+ assert count >=0;
+
+ breakInfo.breaks[count] = mPrimitives.get(idx).location;
+ breakInfo.widths[count] = opt[idx].mWidth;
+ breakInfo.flags [count] = opt[idx].mHasTabs ? TAB_MASK : 0;
+ idx = opt[idx].mPrev;
+ }
+ }
+
+ private static void resize(LineBreaks lineBreaks, int size) {
+ if (lineBreaks.breaks.length == size) {
+ return;
+ }
+ int[] breaks = new int[size];
+ float[] widths = new float[size];
+ int[] flags = new int[size];
+
+ int toCopy = Math.min(size, lineBreaks.breaks.length);
+ System.arraycopy(lineBreaks.breaks, 0, breaks, 0, toCopy);
+ System.arraycopy(lineBreaks.widths, 0, widths, 0, toCopy);
+ System.arraycopy(lineBreaks.flags, 0, flags, 0, toCopy);
+
+ lineBreaks.breaks = breaks;
+ lineBreaks.widths = widths;
+ lineBreaks.flags = flags;
+ }
+
+ @NonNull
+ private LineMetrics computeMetrics(int start, int end) {
+ boolean f = false;
+ float w = 0, pw = 0;
+ for (int i = start; i < end; i++) {
+ Primitive p = mPrimitives.get(i);
+ if (p.type == PrimitiveType.BOX || p.type == PrimitiveType.GLUE) {
+ w += p.width;
+ if (p.type == PrimitiveType.BOX) {
+ pw = w;
+ }
+ } else if (p.type == PrimitiveType.VARIABLE) {
+ w = mTabStops.width(w);
+ f = true;
+ }
+ }
+ return new LineMetrics(w, pw, f);
+ }
+
+ private static float computeDemerits(float maxWidth, float width, boolean finalBreak,
+ float penalty) {
+ float deviation = finalBreak ? 0 : maxWidth - width;
+ return (deviation * deviation) + penalty;
+ }
+
+ /**
+ * @return the last break position or -1 if failed.
+ */
+ @SuppressWarnings("ConstantConditions") // method too complex to be analyzed.
+ private int desperateBreak(int start, int limit, float maxWidth,
+ @NonNull LineMetrics lineMetrics) {
+ float w = 0, pw = 0;
+ boolean breakFound = false;
+ int breakIndex = 0, firstTabIndex = Integer.MAX_VALUE;
+ for (int i = start; i < limit; i++) {
+ Primitive p = mPrimitives.get(i);
+
+ if (p.type == PrimitiveType.BOX || p.type == PrimitiveType.GLUE) {
+ w += p.width;
+ if (p.type == PrimitiveType.BOX) {
+ pw = w;
+ }
+ } else if (p.type == PrimitiveType.VARIABLE) {
+ w = mTabStops.width(w);
+ firstTabIndex = Math.min(firstTabIndex, i);
+ }
+
+ if (pw > maxWidth && breakFound) {
+ break;
+ }
+
+ // must make progress
+ if (i > start &&
+ (p.type == PrimitiveType.PENALTY || p.type == PrimitiveType.WORD_BREAK)) {
+ breakFound = true;
+ breakIndex = i;
+ }
+ }
+
+ if (breakFound) {
+ lineMetrics.mWidth = w;
+ lineMetrics.mPrintedWidth = pw;
+ lineMetrics.mHasTabs = (start <= firstTabIndex && firstTabIndex < breakIndex);
+ return breakIndex;
+ } else {
+ return -1;
+ }
+ }
+
+ private static class LineMetrics {
+ /** Actual width of the line. */
+ float mWidth;
+ /** Width of the line minus trailing whitespace. */
+ float mPrintedWidth;
+ boolean mHasTabs;
+
+ public LineMetrics() {
+ }
+
+ public LineMetrics(float width, float printedWidth, boolean hasTabs) {
+ mWidth = width;
+ mPrintedWidth = printedWidth;
+ mHasTabs = hasTabs;
+ }
+ }
+
+ /**
+ * A struct to store the info about a break.
+ */
+ @SuppressWarnings("SpellCheckingInspection") // For the word struct.
+ private static class Node {
+ // -1 for the first node.
+ int mPrev;
+ // number of breaks so far.
+ int mPrevCount;
+ float mDemerits;
+ float mWidth;
+ boolean mHasTabs;
+
+ public Node(int prev, int prevCount, float demerits, float width, boolean hasTabs) {
+ mPrev = prev;
+ mPrevCount = prevCount;
+ mDemerits = demerits;
+ mWidth = width;
+ mHasTabs = hasTabs;
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/text/Primitive.java b/tools/layoutlib/bridge/src/android/text/Primitive.java
new file mode 100644
index 0000000..37ed072
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/text/Primitive.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.annotation.NonNull;
+
+// Based on the native implementation of Primitive in
+// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260
+public class Primitive {
+ public final @NonNull PrimitiveType type;
+ public final int location;
+ // The following fields don't make sense for all types.
+ // Box and Glue have width only.
+ // Penalty has both width and penalty.
+ // Word_break has penalty only.
+ public final float width;
+ public final float penalty;
+
+ /**
+ * Use {@code PrimitiveType#getNewPrimitive()}
+ */
+ private Primitive(@NonNull PrimitiveType type, int location, float width, float penalty) {
+ this.type = type;
+ this.location = location;
+ this.width = width;
+ this.penalty = penalty;
+ }
+
+ public static enum PrimitiveType {
+ /**
+ * Something with a constant width that is to be typeset - like a character.
+ */
+ BOX,
+ /**
+ * Blank space with fixed width.
+ */
+ GLUE,
+ /**
+ * Aesthetic cost indicating how desirable breaking at this point will be. A penalty of
+ * {@link #PENALTY_INFINITY} means a forced non-break, whereas a penalty of negative
+ * {@code #PENALTY_INFINITY} means a forced break.
+ * <p/>
+ * Currently, it only stores penalty with values 0 or -infinity.
+ */
+ PENALTY,
+ /**
+ * For tabs - variable width space.
+ */
+ VARIABLE,
+ /**
+ * Possible breakpoints within a word. Think of this as a high cost {@link #PENALTY}.
+ */
+ WORD_BREAK;
+
+ public Primitive getNewPrimitive(int location) {
+ assert this == VARIABLE;
+ return new Primitive(this, location, 0f, 0f);
+ }
+
+ public Primitive getNewPrimitive(int location, float value) {
+ assert this == BOX || this == GLUE || this == WORD_BREAK;
+ if (this == BOX || this == GLUE) {
+ return new Primitive(this, location, value, 0f);
+ } else {
+ return new Primitive(this, location, 0f, value);
+ }
+ }
+
+ public Primitive getNewPrimitive(int location, float width, float penalty) {
+ assert this == PENALTY;
+ return new Primitive(this, location, width, penalty);
+ }
+
+ // forced non-break, negative infinity is forced break.
+ public static final float PENALTY_INFINITY = 1e7f;
+ }
+}
+
diff --git a/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java b/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java
index b0d79a8..1b0ba51 100644
--- a/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java
@@ -1,14 +1,22 @@
package android.text;
+import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-import java.text.CharacterIterator;
+import android.annotation.NonNull;
+import android.graphics.BidiRenderer;
+import android.graphics.Paint;
+import android.graphics.Paint_Delegate;
+import android.graphics.RectF;
+import android.icu.text.BreakIterator;
+import android.icu.util.ULocale;
+import android.text.Primitive.PrimitiveType;
+import android.text.StaticLayout.LineBreaks;
+
+import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Locale;
+import java.util.List;
-import com.ibm.icu.lang.UCharacter;
-import com.ibm.icu.text.BreakIterator;
-import com.ibm.icu.util.ULocale;
import javax.swing.text.Segment;
/**
@@ -20,36 +28,206 @@ import javax.swing.text.Segment;
*/
public class StaticLayout_Delegate {
+ private static final char CHAR_SPACE = 0x20;
+ private static final char CHAR_TAB = 0x09;
+ private static final char CHAR_NEWLINE = 0x0A;
+ private static final char CHAR_ZWSP = 0x200B; // Zero width space.
+
+ // ---- Builder delegate manager ----
+ private static final DelegateManager<Builder> sBuilderManager =
+ new DelegateManager<Builder>(Builder.class);
+
+ @LayoutlibDelegate
+ /*package*/ static long nNewBuilder() {
+ return sBuilderManager.addNewDelegate(new Builder());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nFreeBuilder(long nativeBuilder) {
+ sBuilderManager.removeJavaReferenceFor(nativeBuilder);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nFinishBuilder(long nativeBuilder) {
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nLoadHyphenator(String patternData) {
+ return Hyphenator_Delegate.loadHyphenator(patternData);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nSetLocale(long nativeBuilder, String locale, long nativeHyphenator) {
+ Builder builder = sBuilderManager.getDelegate(nativeBuilder);
+ if (builder != null) {
+ builder.mLocale = locale;
+ builder.mNativeHyphenator = nativeHyphenator;
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nSetIndents(long nativeBuilder, int[] indents) {
+ // TODO.
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nSetupParagraph(long nativeBuilder, char[] text, int length,
+ float firstWidth, int firstWidthLineCount, float restWidth,
+ int[] variableTabStops, int defaultTabStop, int breakStrategy,
+ int hyphenationFrequency) {
+ Builder builder = sBuilderManager.getDelegate(nativeBuilder);
+ if (builder == null) {
+ return;
+ }
+
+ builder.mText = text;
+ builder.mWidths = new float[length];
+ builder.mLineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth);
+ builder.mTabStopCalculator = new TabStops(variableTabStops, defaultTabStop);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nAddStyleRun(long nativeBuilder, long nativePaint, long nativeTypeface,
+ int start, int end, boolean isRtl) {
+ Builder builder = sBuilderManager.getDelegate(nativeBuilder);
+
+ int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
+ return builder == null ? 0 :
+ measureText(nativePaint, builder.mText, start, end - start, builder.mWidths,
+ bidiFlags);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nAddMeasuredRun(long nativeBuilder, int start, int end, float[] widths) {
+ Builder builder = sBuilderManager.getDelegate(nativeBuilder);
+ if (builder != null) {
+ System.arraycopy(widths, start, builder.mWidths, start, end - start);
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nAddReplacementRun(long nativeBuilder, int start, int end, float width) {
+ Builder builder = sBuilderManager.getDelegate(nativeBuilder);
+ if (builder == null) {
+ return;
+ }
+ builder.mWidths[start] = width;
+ Arrays.fill(builder.mWidths, start + 1, end, 0.0f);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nGetWidths(long nativeBuilder, float[] floatsArray) {
+ Builder builder = sBuilderManager.getDelegate(nativeBuilder);
+ if (builder != null) {
+ System.arraycopy(builder.mWidths, 0, floatsArray, 0, builder.mWidths.length);
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nComputeLineBreaks(long nativeBuilder,
+ LineBreaks recycle, int[] recycleBreaks, float[] recycleWidths,
+ int[] recycleFlags, int recycleLength) {
+
+ Builder builder = sBuilderManager.getDelegate(nativeBuilder);
+ if (builder == null) {
+ return 0;
+ }
+
+ // compute all possible breakpoints.
+ int length = builder.mWidths.length;
+ BreakIterator it = BreakIterator.getLineInstance(new ULocale(builder.mLocale));
+ it.setText(new Segment(builder.mText, 0, length));
+
+ // average word length in english is 5. So, initialize the possible breaks with a guess.
+ List<Integer> breaks = new ArrayList<Integer>((int) Math.ceil(length / 5d));
+ int loc;
+ it.first();
+ while ((loc = it.next()) != BreakIterator.DONE) {
+ breaks.add(loc);
+ }
+
+ List<Primitive> primitives =
+ computePrimitives(builder.mText, builder.mWidths, length, breaks);
+ switch (builder.mBreakStrategy) {
+ case Layout.BREAK_STRATEGY_SIMPLE:
+ builder.mLineBreaker = new GreedyLineBreaker(primitives, builder.mLineWidth,
+ builder.mTabStopCalculator);
+ break;
+ case Layout.BREAK_STRATEGY_HIGH_QUALITY:
+ // TODO
+// break;
+ case Layout.BREAK_STRATEGY_BALANCED:
+ builder.mLineBreaker = new OptimizingLineBreaker(primitives, builder.mLineWidth,
+ builder.mTabStopCalculator);
+ break;
+ default:
+ throw new AssertionError("Unknown break strategy: " + builder.mBreakStrategy);
+ }
+ builder.mLineBreaker.computeBreaks(recycle);
+ return recycle.breaks.length;
+ }
+
/**
- * Fills the recycle array with positions that are suitable to break the text at. The array
- * must be terminated by '-1'.
+ * Compute metadata each character - things which help in deciding if it's possible to break
+ * at a point or not.
*/
- @LayoutlibDelegate
- /*package*/ static int[] nLineBreakOpportunities(String locale, char[] text, int length,
- int[] recycle) {
- BreakIterator iterator = BreakIterator.getLineInstance(new ULocale(locale));
- Segment segment = new Segment(text, 0, length);
- iterator.setText(segment);
- if (recycle == null) {
- // Because 42 is the answer to everything.
- recycle = new int[42];
- }
- int breakOpp = iterator.first();
- recycle[0] = breakOpp;
- //noinspection ConstantConditions
- assert BreakIterator.DONE == -1;
- for (int i = 1; breakOpp != BreakIterator.DONE; ++i) {
- if (i >= recycle.length) {
- recycle = doubleSize(recycle);
+ @NonNull
+ private static List<Primitive> computePrimitives(@NonNull char[] text, @NonNull float[] widths,
+ int length, @NonNull List<Integer> breaks) {
+ // Initialize the list with a guess of the number of primitives:
+ // 2 Primitives per non-whitespace char and approx 5 chars per word (i.e. 83% chars)
+ List<Primitive> primitives = new ArrayList<Primitive>(((int) Math.ceil(length * 1.833)));
+ int breaksSize = breaks.size();
+ int breakIndex = 0;
+ for (int i = 0; i < length; i++) {
+ char c = text[i];
+ if (c == CHAR_SPACE || c == CHAR_ZWSP) {
+ primitives.add(PrimitiveType.GLUE.getNewPrimitive(i, widths[i]));
+ } else if (c == CHAR_TAB) {
+ primitives.add(PrimitiveType.VARIABLE.getNewPrimitive(i));
+ } else if (c != CHAR_NEWLINE) {
+ while (breakIndex < breaksSize && breaks.get(breakIndex) < i) {
+ breakIndex++;
+ }
+ Primitive p;
+ if (widths[i] != 0) {
+ if (breakIndex < breaksSize && breaks.get(breakIndex) == i) {
+ p = PrimitiveType.PENALTY.getNewPrimitive(i, 0, 0);
+ } else {
+ p = PrimitiveType.WORD_BREAK.getNewPrimitive(i, 0);
+ }
+ primitives.add(p);
+ }
+
+ primitives.add(PrimitiveType.BOX.getNewPrimitive(i, widths[i]));
}
- assert (i < recycle.length);
- breakOpp = iterator.next();
- recycle[i] = breakOpp;
}
- return recycle;
+ // final break at end of everything
+ primitives.add(
+ PrimitiveType.PENALTY.getNewPrimitive(length, 0, -PrimitiveType.PENALTY_INFINITY));
+ return primitives;
}
- private static int[] doubleSize(int[] array) {
- return Arrays.copyOf(array, array.length * 2);
+ private static float measureText(long nativePaint, char []text, int index, int count,
+ float[] widths, int bidiFlags) {
+ Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint);
+ RectF bounds = new BidiRenderer(null, paint, text)
+ .renderText(index, index + count, bidiFlags, widths, 0, false);
+ return bounds.right - bounds.left;
+ }
+
+ // TODO: Rename to LineBreakerRef and move everything other than LineBreaker to LineBreaker.
+ /**
+ * Java representation of the native Builder class.
+ */
+ private static class Builder {
+ String mLocale;
+ char[] mText;
+ float[] mWidths;
+ LineBreaker mLineBreaker;
+ long mNativeHyphenator;
+ int mBreakStrategy;
+ LineWidth mLineWidth;
+ TabStops mTabStopCalculator;
}
}
diff --git a/tools/layoutlib/bridge/src/android/text/TabStops.java b/tools/layoutlib/bridge/src/android/text/TabStops.java
new file mode 100644
index 0000000..6c2f1e1
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/text/TabStops.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.annotation.Nullable;
+
+// Based on the native implementation of TabStops in
+// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260
+public class TabStops {
+ @Nullable
+ private int[] mStops;
+ private final int mTabWidth;
+
+ public TabStops(@Nullable int[] stops, int defaultTabWidth) {
+ mTabWidth = defaultTabWidth;
+ mStops = stops;
+ }
+
+ public float width(float widthSoFar) {
+ if (mStops != null) {
+ for (int i : mStops) {
+ if (i > widthSoFar) {
+ return i;
+ }
+ }
+ }
+ // find the next tabStop after widthSoFar.
+ return (int) ((widthSoFar + mTabWidth) / mTabWidth) * mTabWidth;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/util/Xml_Delegate.java b/tools/layoutlib/bridge/src/android/util/Xml_Delegate.java
index a193330..213e848 100644
--- a/tools/layoutlib/bridge/src/android/util/Xml_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/util/Xml_Delegate.java
@@ -17,9 +17,9 @@
package android.util;
import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.ParserFactory;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-import org.kxml2.io.KXmlParser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -37,11 +37,7 @@ public class Xml_Delegate {
@LayoutlibDelegate
/*package*/ static XmlPullParser newPullParser() {
try {
- KXmlParser parser = new KXmlParser();
- // The prebuilt kxml2 library with the IDE doesn't support DOCECL.
-// parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
- parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
- return parser;
+ return ParserFactory.instantiateParser(null);
} catch (XmlPullParserException e) {
throw new AssertionError();
}
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index 2e649c3..6767a07 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -22,6 +22,7 @@ import com.android.ide.common.rendering.api.MergeCookie;
import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.BridgeConstants;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
import com.android.layoutlib.bridge.impl.ParserFactory;
@@ -53,9 +54,14 @@ public final class BridgeInflater extends LayoutInflater {
*/
private static final String[] sClassPrefixList = {
"android.widget.",
- "android.webkit."
+ "android.webkit.",
+ "android.app."
};
+ public static String[] getClassPrefixList() {
+ return sClassPrefixList;
+ }
+
protected BridgeInflater(LayoutInflater original, Context newContext) {
super(original, newContext);
newContext = getBaseContext(newContext);
@@ -127,11 +133,11 @@ public final class BridgeInflater extends LayoutInflater {
}
@Override
- public View createViewFromTag(View parent, String name, AttributeSet attrs,
- boolean inheritContext) {
+ public View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
+ boolean ignoreThemeAttrs) {
View view;
try {
- view = super.createViewFromTag(parent, name, attrs, inheritContext);
+ view = super.createViewFromTag(parent, name, context, attrs, ignoreThemeAttrs);
} catch (InflateException e) {
// try to load the class from using the custom view loader
try {
@@ -229,6 +235,13 @@ public final class BridgeInflater extends LayoutInflater {
if (viewKey != null) {
bc.addViewKey(view, viewKey);
}
+ String scrollPos = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollY");
+ if (scrollPos != null) {
+ if (scrollPos.endsWith("px")) {
+ int value = Integer.parseInt(scrollPos.substring(0, scrollPos.length() - 2));
+ bc.setScrollYPos(view, value);
+ }
+ }
}
}
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 5176419..82012c1 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -17,6 +17,7 @@
package android.view;
import android.graphics.Point;
+import com.android.internal.app.IAssistScreenshotReceiver;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
@@ -215,6 +216,12 @@ public class IWindowManagerImpl implements IWindowManager {
}
@Override
+ public void overridePendingAppTransitionClipReveal(int startX, int startY,
+ int startWidth, int startHeight) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
public void overridePendingAppTransitionThumb(Bitmap srcThumb, int startX, int startY,
IRemoteCallback startedCallback, boolean scaleUp) throws RemoteException {
// TODO Auto-generated method stub
@@ -269,8 +276,15 @@ public class IWindowManagerImpl implements IWindowManager {
}
@Override
- public Bitmap screenshotApplications(IBinder arg0, int displayId, int arg1,
- int arg2, boolean arg3) throws RemoteException {
+ public boolean requestAssistScreenshot(IAssistScreenshotReceiver receiver)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth,
+ int maxHeight) throws RemoteException {
// TODO Auto-generated method stub
return null;
}
@@ -293,7 +307,7 @@ public class IWindowManagerImpl implements IWindowManager {
}
@Override
- public void setAppGroupId(IBinder arg0, int arg1) throws RemoteException {
+ public void setAppTask(IBinder arg0, int arg1) throws RemoteException {
// TODO Auto-generated method stub
}
@@ -362,6 +376,10 @@ public class IWindowManagerImpl implements IWindowManager {
}
@Override
+ public void setForcedDisplayScalingMode(int displayId, int mode) {
+ }
+
+ @Override
public void setInTouchMode(boolean arg0) throws RemoteException {
// TODO Auto-generated method stub
}
diff --git a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java
index 7a73fae..27b406a 100644
--- a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java
@@ -21,9 +21,11 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.content.Context;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.util.AttributeSet;
+import android.util.TypedValue;
import android.util.Xml;
import java.io.IOException;
@@ -36,9 +38,13 @@ import java.io.IOException;
*
*/
public class LayoutInflater_Delegate {
-
private static final String TAG_MERGE = "merge";
+ private static final String ATTR_LAYOUT = "layout";
+
+ private static final int[] ATTRS_THEME = new int[] {
+ com.android.internal.R.attr.theme };
+
public static boolean sIsInInclude = false;
/**
@@ -49,7 +55,7 @@ public class LayoutInflater_Delegate {
*/
@LayoutlibDelegate
/* package */ static void rInflate(LayoutInflater thisInflater, XmlPullParser parser,
- View parent, final AttributeSet attrs, boolean finishInflate, boolean inheritContext)
+ View parent, Context context, AttributeSet attrs, boolean finishInflate)
throws XmlPullParserException, IOException {
if (finishInflate == false) {
@@ -61,7 +67,7 @@ public class LayoutInflater_Delegate {
// ---- START DEFAULT IMPLEMENTATION.
- thisInflater.rInflate_Original(parser, parent, attrs, finishInflate, inheritContext);
+ thisInflater.rInflate_Original(parser, parent, context, attrs, finishInflate);
// ---- END DEFAULT IMPLEMENTATION.
@@ -74,15 +80,50 @@ public class LayoutInflater_Delegate {
}
@LayoutlibDelegate
- public static void parseInclude(LayoutInflater thisInflater, XmlPullParser parser, View parent,
- AttributeSet attrs, boolean inheritContext) throws XmlPullParserException, IOException {
-
+ public static void parseInclude(LayoutInflater thisInflater, XmlPullParser parser,
+ Context context, View parent, AttributeSet attrs)
+ throws XmlPullParserException, IOException {
int type;
if (parent instanceof ViewGroup) {
- final int layout = attrs.getAttributeResourceValue(null, "layout", 0);
+ // Apply a theme wrapper, if requested. This is sort of a weird
+ // edge case, since developers think the <include> overwrites
+ // values in the AttributeSet of the included View. So, if the
+ // included View has a theme attribute, we'll need to ignore it.
+ final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
+ final int themeResId = ta.getResourceId(0, 0);
+ final boolean hasThemeOverride = themeResId != 0;
+ if (hasThemeOverride) {
+ context = new ContextThemeWrapper(context, themeResId);
+ }
+ ta.recycle();
+
+ // If the layout is pointing to a theme attribute, we have to
+ // massage the value to get a resource identifier out of it.
+ int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
+ if (layout == 0) {
+ final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
+ if (value == null || value.length() <= 0) {
+ throw new InflateException("You must specify a layout in the"
+ + " include tag: <include layout=\"@layout/layoutID\" />");
+ }
+
+ // Attempt to resolve the "?attr/name" string to an identifier.
+ layout = context.getResources().getIdentifier(value.substring(1), null, null);
+ }
+
+ // The layout might be referencing a theme attribute.
+ // ---- START CHANGES
+ if (layout != 0) {
+ final TypedValue tempValue = new TypedValue();
+ if (context.getTheme().resolveAttribute(layout, tempValue, true)) {
+ layout = tempValue.resourceId;
+ }
+ }
+ // ---- END CHANGES
+
if (layout == 0) {
- final String value = attrs.getAttributeValue(null, "layout");
+ final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
if (value == null) {
throw new InflateException("You must specifiy a layout in the"
+ " include tag: <include layout=\"@layout/layoutID\" />");
@@ -111,13 +152,20 @@ public class LayoutInflater_Delegate {
if (TAG_MERGE.equals(childName)) {
// Inflate all children.
- thisInflater.rInflate(childParser, parent, childAttrs, false,
- inheritContext);
+ thisInflater.rInflate(childParser, parent, context, childAttrs, false);
} else {
final View view = thisInflater.createViewFromTag(parent, childName,
- childAttrs, inheritContext);
+ context, childAttrs, hasThemeOverride);
final ViewGroup group = (ViewGroup) parent;
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.Include);
+ final int id = a.getResourceId(
+ com.android.internal.R.styleable.Include_id, View.NO_ID);
+ final int visibility = a.getInt(
+ com.android.internal.R.styleable.Include_visibility, -1);
+ a.recycle();
+
// We try to load the layout params set in the <include /> tag. If
// they don't exist, we will rely on the layout params set in the
// included XML file.
@@ -133,34 +181,20 @@ public class LayoutInflater_Delegate {
// ---- END CHANGES
params = group.generateLayoutParams(attrs);
-
- } catch (RuntimeException e) {
- // ---- START CHANGES
- sIsInInclude = false;
- // ---- END CHANGES
-
- params = group.generateLayoutParams(childAttrs);
+ } catch (RuntimeException ignored) {
+ // Ignore, just fail over to child attrs.
} finally {
// ---- START CHANGES
sIsInInclude = false;
// ---- END CHANGES
-
- if (params != null) {
- view.setLayoutParams(params);
- }
}
+ if (params == null) {
+ params = group.generateLayoutParams(childAttrs);
+ }
+ view.setLayoutParams(params);
// Inflate all children.
- thisInflater.rInflate(childParser, view, childAttrs, true, true);
-
- // Attempt to override the included layout's android:id with the
- // one set on the <include /> tag itself.
- TypedArray a = thisInflater.mContext.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.View, 0, 0);
- int id = a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID);
- // While we're at it, let's try to override android:visibility.
- int visibility = a.getInt(com.android.internal.R.styleable.View_visibility, -1);
- a.recycle();
+ thisInflater.rInflateChildren(childParser, view, childAttrs, true);
if (id != View.NO_ID) {
view.setId(id);
@@ -188,12 +222,6 @@ public class LayoutInflater_Delegate {
throw new InflateException("<include /> can only be used inside of a ViewGroup");
}
- final int currentDepth = parser.getDepth();
- while (((type = parser.next()) != XmlPullParser.END_TAG ||
- parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
- // Empty
- }
+ LayoutInflater.consumeChildElements(parser);
}
-
-
}
diff --git a/tools/layoutlib/bridge/src/android/view/ShadowPainter.java b/tools/layoutlib/bridge/src/android/view/ShadowPainter.java
index 2f93bc8..f09fffd 100644
--- a/tools/layoutlib/bridge/src/android/view/ShadowPainter.java
+++ b/tools/layoutlib/bridge/src/android/view/ShadowPainter.java
@@ -16,7 +16,7 @@
package android.view;
-import com.android.annotations.NonNull;
+import android.annotation.NonNull;
import java.awt.Graphics2D;
import java.awt.Image;
diff --git a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
index e72a0db..51d32e3 100644
--- a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
@@ -46,16 +46,12 @@ public class ViewGroup_Delegate {
/*package*/ static boolean drawChild(ViewGroup thisVG, Canvas canvas, View child,
long drawingTime) {
if (child.getZ() > thisVG.getZ()) {
+ // The background's bounds are set lazily. Make sure they are set correctly so that
+ // the outline obtained is correct.
+ child.setBackgroundBounds();
ViewOutlineProvider outlineProvider = child.getOutlineProvider();
- Outline outline = new Outline();
+ Outline outline = child.mAttachInfo.mTmpOutline;
outlineProvider.getOutline(child, outline);
- if (outline.mPath == null && outline.mRect == null) {
- // Sometimes, the bounds of the background drawable are not set until View.draw()
- // is called. So, we set the bounds manually and try to get the outline again.
- child.getBackground().setBounds(0, 0, child.mRight - child.mLeft,
- child.mBottom - child.mTop);
- outlineProvider.getOutline(child, outline);
- }
if (outline.mPath != null || (outline.mRect != null && !outline.mRect.isEmpty())) {
int restoreTo = transformCanvas(thisVG, canvas, child);
drawShadow(thisVG, canvas, child, outline);
diff --git a/tools/layoutlib/bridge/src/android/view/View_Delegate.java b/tools/layoutlib/bridge/src/android/view/View_Delegate.java
index 8215f7c..408ec54 100644
--- a/tools/layoutlib/bridge/src/android/view/View_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/View_Delegate.java
@@ -16,8 +16,12 @@
package android.view;
+import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import android.content.Context;
+import android.os.IBinder;
+
/**
* Delegate used to provide new implementation of a select few methods of {@link View}
*
@@ -31,4 +35,13 @@ public class View_Delegate {
/*package*/ static boolean isInEditMode(View thisView) {
return true;
}
+
+ @LayoutlibDelegate
+ /*package*/ static IBinder getWindowToken(View thisView) {
+ Context baseContext = BridgeContext.getBaseContext(thisView.getContext());
+ if (baseContext instanceof BridgeContext) {
+ return ((BridgeContext) baseContext).getBinder();
+ }
+ return null;
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/view/WindowCallback.java b/tools/layoutlib/bridge/src/android/view/WindowCallback.java
index 78242a8..d691c8e 100644
--- a/tools/layoutlib/bridge/src/android/view/WindowCallback.java
+++ b/tools/layoutlib/bridge/src/android/view/WindowCallback.java
@@ -110,6 +110,11 @@ public class WindowCallback implements Window.Callback {
}
@Override
+ public boolean onSearchRequested(SearchEvent searchEvent) {
+ return onSearchRequested();
+ }
+
+ @Override
public boolean onSearchRequested() {
return false;
}
@@ -120,6 +125,11 @@ public class WindowCallback implements Window.Callback {
}
@Override
+ public ActionMode onWindowStartingActionMode(Callback callback, int type) {
+ return null;
+ }
+
+ @Override
public void onActionModeStarted(ActionMode mode) {
}
diff --git a/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java b/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java
index d5170aa..edb5eff 100644
--- a/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java
+++ b/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java
@@ -16,9 +16,8 @@
package android.view.accessibility;
-import com.android.annotations.NonNull;
-
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.ServiceInfo;
import android.view.IWindow;
diff --git a/tools/layoutlib/bridge/src/com/android/internal/policy/PolicyManager.java b/tools/layoutlib/bridge/src/com/android/internal/policy/PolicyManager.java
deleted file mode 100644
index 6558b6a..0000000
--- a/tools/layoutlib/bridge/src/com/android/internal/policy/PolicyManager.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.policy;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.RenderAction;
-
-import android.content.Context;
-import android.view.BridgeInflater;
-import android.view.FallbackEventHandler;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.Window;
-import android.view.WindowManagerPolicy;
-
-/**
- * Custom implementation of PolicyManager that does nothing to run in LayoutLib.
- *
- */
-public class PolicyManager {
-
- public static Window makeNewWindow(Context context) {
- // this will likely crash somewhere beyond so we log it.
- Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
- "Call to PolicyManager.makeNewWindow is not supported", null);
- return null;
- }
-
- public static LayoutInflater makeNewLayoutInflater(Context context) {
- return new BridgeInflater(context, RenderAction.getCurrentContext().getLayoutlibCallback());
- }
-
- public static WindowManagerPolicy makeNewWindowManager() {
- // this will likely crash somewhere beyond so we log it.
- Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
- "Call to PolicyManager.makeNewWindowManager is not supported", null);
- return null;
- }
-
- public static FallbackEventHandler makeNewFallbackEventHandler(Context context) {
- return new FallbackEventHandler() {
- @Override
- public void setView(View v) {
- }
-
- @Override
- public void preDispatchKeyEvent(KeyEvent event) {
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- return false;
- }
- };
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index c6d60f8..3ae10f2 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -16,10 +16,6 @@
package com.android.layoutlib.bridge;
-import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
-import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
-
-import com.android.annotations.NonNull;
import com.android.ide.common.rendering.api.Capability;
import com.android.ide.common.rendering.api.DrawableParams;
import com.android.ide.common.rendering.api.Features;
@@ -36,13 +32,13 @@ import com.android.resources.ResourceType;
import com.android.tools.layoutlib.create.MethodAdapter;
import com.android.tools.layoutlib.create.OverrideMethod;
import com.android.util.Pair;
-import com.ibm.icu.util.ULocale;
-import libcore.io.MemoryMappedFile_Delegate;
+import android.annotation.NonNull;
import android.content.res.BridgeAssetManager;
import android.graphics.Bitmap;
import android.graphics.FontFamily_Delegate;
import android.graphics.Typeface_Delegate;
+import android.icu.util.ULocale;
import android.os.Looper;
import android.os.Looper_Accessor;
import android.view.View;
@@ -60,6 +56,11 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
+import libcore.io.MemoryMappedFile_Delegate;
+
+import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
+import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
+
/**
* Main entry point of the LayoutLib Bridge.
* <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java
index ea5f1ea..e589d9e 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java
@@ -16,9 +16,9 @@
package com.android.layoutlib.bridge.android;
-import java.util.Locale;
+import android.icu.util.ULocale;
-import com.ibm.icu.util.ULocale;
+import java.util.Locale;
/**
* This class provides an alternate implementation for {@code java.util.Locale#toLanguageTag}
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 2cbbeba..6be5a95 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
@@ -16,8 +16,7 @@
package com.android.layoutlib.bridge.android;
-import android.os.IBinder;
-import com.android.annotations.Nullable;
+import com.android.SdkConstants;
import com.android.ide.common.rendering.api.AssetRepository;
import com.android.ide.common.rendering.api.ILayoutPullParser;
import com.android.ide.common.rendering.api.LayoutLog;
@@ -37,6 +36,8 @@ import com.android.util.Pair;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.Nullable;
+import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -63,10 +64,15 @@ import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
import android.net.Uri;
+import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.IInterface;
import android.os.Looper;
+import android.os.Parcel;
import android.os.PowerManager;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -74,6 +80,7 @@ import android.util.TypedValue;
import android.view.BridgeInflater;
import android.view.Display;
import android.view.DisplayAdjustments;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
@@ -81,6 +88,7 @@ import android.view.accessibility.AccessibilityManager;
import android.view.textservice.TextServicesManager;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -118,6 +126,7 @@ public final class BridgeContext extends Context {
private final LayoutlibCallback mLayoutlibCallback;
private final WindowManager mWindowManager;
private final DisplayManager mDisplayManager;
+ private final HashMap<View, Integer> mScrollYPos = new HashMap<View, Integer>();
private Resources.Theme mTheme;
@@ -125,6 +134,7 @@ public final class BridgeContext extends Context {
new IdentityHashMap<Object, Map<String,String>>();
// maps for dynamically generated id representing style objects (StyleResourceValue)
+ @Nullable
private Map<Integer, StyleResourceValue> mDynamicIdToStyleMap;
private Map<StyleResourceValue, Integer> mStyleToDynamicIdMap;
private int mDynamicIdGenerator = 0x02030000; // Base id for R.style in custom namespace
@@ -137,8 +147,33 @@ public final class BridgeContext extends Context {
private final Stack<BridgeXmlBlockParser> mParserStack = new Stack<BridgeXmlBlockParser>();
private SharedPreferences mSharedPreferences;
+ private ClassLoader mClassLoader;
+ private IBinder mBinder;
+
+
+ /**
+ * Some applications that target both pre API 17 and post API 17, set the newer attrs to
+ * reference the older ones. For example, android:paddingStart will resolve to
+ * android:paddingLeft. This way the apps need to only define paddingLeft at any other place.
+ * This a map from value to attribute name. Warning for missing references shouldn't be logged
+ * if value and attr name pair is the same as an entry in this map.
+ */
+ private static Map<String, String> RTL_ATTRS = new HashMap<String, String>(10);
+
+ static {
+ RTL_ATTRS.put("?android:attr/paddingLeft", "paddingStart");
+ RTL_ATTRS.put("?android:attr/paddingRight", "paddingEnd");
+ RTL_ATTRS.put("?android:attr/layout_marginLeft", "layout_marginStart");
+ RTL_ATTRS.put("?android:attr/layout_marginRight", "layout_marginEnd");
+ RTL_ATTRS.put("?android:attr/layout_toLeft", "layout_toStartOf");
+ RTL_ATTRS.put("?android:attr/layout_toRight", "layout_toEndOf");
+ RTL_ATTRS.put("?android:attr/layout_alignParentLeft", "layout_alignParentStart");
+ RTL_ATTRS.put("?android:attr/layout_alignParentRight", "layout_alignParentEnd");
+ RTL_ATTRS.put("?android:attr/drawableLeft", "drawableStart");
+ RTL_ATTRS.put("?android:attr/drawableRight", "drawableEnd");
+ }
- /**
+ /**
* @param projectKey An Object identifying the project. This is used for the cache mechanism.
* @param metrics the {@link DisplayMetrics}.
* @param renderResources the configured resources (both framework and projects) for this
@@ -461,7 +496,35 @@ public final class BridgeContext extends Context {
@Override
public ClassLoader getClassLoader() {
- return this.getClass().getClassLoader();
+ // The documentation for this method states that it should return a class loader one can
+ // use to retrieve classes in this package. However, when called by LayoutInflater, we do
+ // not want the class loader to return app's custom views.
+ // This is so that the IDE can instantiate the custom views and also generate proper error
+ // messages in case of failure. This also enables the IDE to fallback to MockView in case
+ // there's an exception thrown when trying to inflate the custom view.
+ // To work around this issue, LayoutInflater is modified via LayoutLib Create tool to
+ // replace invocations of this method to a new method: getFrameworkClassLoader(). Also,
+ // the method is injected into Context. The implementation of getFrameworkClassLoader() is:
+ // "return getClass().getClassLoader();". This means that when LayoutInflater asks for
+ // the context ClassLoader, it gets only LayoutLib's ClassLoader which doesn't have
+ // access to the apps's custom views.
+ // This method can now return the right ClassLoader, which CustomViews can use to do the
+ // right thing.
+ if (mClassLoader == null) {
+ mClassLoader = new ClassLoader(getClass().getClassLoader()) {
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ for (String prefix : BridgeInflater.getClassPrefixList()) {
+ if (name.startsWith(prefix)) {
+ // These are framework classes and should not be loaded from the app.
+ throw new ClassNotFoundException(name + " not found");
+ }
+ }
+ return BridgeContext.this.mLayoutlibCallback.findClass(name);
+ }
+ };
+ }
+ return mClassLoader;
}
@Override
@@ -499,6 +562,34 @@ public final class BridgeContext extends Context {
throw new UnsupportedOperationException("Unsupported Service: " + service);
}
+ @Override
+ public String getSystemServiceName(Class<?> serviceClass) {
+ if (serviceClass.equals(LayoutInflater.class)) {
+ return LAYOUT_INFLATER_SERVICE;
+ }
+
+ if (serviceClass.equals(TextServicesManager.class)) {
+ return TEXT_SERVICES_MANAGER_SERVICE;
+ }
+
+ if (serviceClass.equals(WindowManager.class)) {
+ return WINDOW_SERVICE;
+ }
+
+ if (serviceClass.equals(PowerManager.class)) {
+ return POWER_SERVICE;
+ }
+
+ if (serviceClass.equals(DisplayManager.class)) {
+ return DISPLAY_SERVICE;
+ }
+
+ if (serviceClass.equals(AccessibilityManager.class)) {
+ return ACCESSIBILITY_SERVICE;
+ }
+
+ throw new UnsupportedOperationException("Unsupported Service: " + serviceClass);
+ }
@Override
public final BridgeTypedArray obtainStyledAttributes(int[] attrs) {
@@ -664,44 +755,48 @@ public final class BridgeContext extends Context {
}
}
} else if (defStyleRes != 0) {
- boolean isFrameworkRes = true;
- Pair<ResourceType, String> value = Bridge.resolveResourceId(defStyleRes);
- if (value == null) {
- value = mLayoutlibCallback.resolveResourceId(defStyleRes);
- isFrameworkRes = false;
- }
+ StyleResourceValue item = getStyleByDynamicId(defStyleRes);
+ if (item != null) {
+ defStyleValues = item;
+ } else {
+ boolean isFrameworkRes = true;
+ Pair<ResourceType, String> value = Bridge.resolveResourceId(defStyleRes);
+ if (value == null) {
+ value = mLayoutlibCallback.resolveResourceId(defStyleRes);
+ isFrameworkRes = false;
+ }
- if (value != null) {
- if ((value.getFirst() == ResourceType.STYLE)) {
- // look for the style in all resources:
- StyleResourceValue item = mRenderResources.getStyle(value.getSecond(),
- isFrameworkRes);
- if (item != null) {
- if (defaultPropMap != null) {
- defaultPropMap.put("style", item.getName());
+ if (value != null) {
+ if ((value.getFirst() == ResourceType.STYLE)) {
+ // look for the style in all resources:
+ item = mRenderResources.getStyle(value.getSecond(), isFrameworkRes);
+ if (item != null) {
+ if (defaultPropMap != null) {
+ defaultPropMap.put("style", item.getName());
+ }
+
+ defStyleValues = item;
+ } else {
+ Bridge.getLog().error(null,
+ String.format(
+ "Style with id 0x%x (resolved to '%s') does not exist.",
+ defStyleRes, value.getSecond()),
+ null);
}
-
- defStyleValues = item;
} else {
Bridge.getLog().error(null,
String.format(
- "Style with id 0x%x (resolved to '%s') does not exist.",
- defStyleRes, value.getSecond()),
+ "Resource id 0x%x is not of type STYLE (instead %s)",
+ defStyleRes, value.getFirst().toString()),
null);
}
} else {
Bridge.getLog().error(null,
String.format(
- "Resource id 0x%x is not of type STYLE (instead %s)",
- defStyleRes, value.getFirst().toString()),
+ "Failed to find style with id 0x%x in current theme",
+ defStyleRes),
null);
}
- } else {
- Bridge.getLog().error(null,
- String.format(
- "Failed to find style with id 0x%x in current theme",
- defStyleRes),
- null);
}
}
@@ -762,6 +857,22 @@ public final class BridgeContext extends Context {
}
resValue = mRenderResources.resolveResValue(resValue);
+
+ // If the value is a reference to another theme attribute that doesn't
+ // exist, we should log a warning and omit it.
+ String val = resValue.getValue();
+ if (val != null && val.startsWith(SdkConstants.PREFIX_THEME_REF)) {
+ if (!attrName.equals(RTL_ATTRS.get(val)) ||
+ getApplicationInfo().targetSdkVersion <
+ VERSION_CODES.JELLY_BEAN_MR1) {
+ // Only log a warning if the referenced value isn't one of the RTL
+ // attributes, or the app targets old API.
+ Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR,
+ String.format("Failed to find '%s' in current theme.", val),
+ val);
+ }
+ resValue = null;
+ }
}
ta.bridgeSetValue(index, attrName, frameworkAttr, resValue);
@@ -952,6 +1063,61 @@ public final class BridgeContext extends Context {
return context;
}
+ public IBinder getBinder() {
+ if (mBinder == null) {
+ // create a dummy binder. We only need it be not null.
+ mBinder = new IBinder() {
+ @Override
+ public String getInterfaceDescriptor() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean pingBinder() {
+ return false;
+ }
+
+ @Override
+ public boolean isBinderAlive() {
+ return false;
+ }
+
+ @Override
+ public IInterface queryLocalInterface(String descriptor) {
+ return null;
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, String[] args) throws RemoteException {
+
+ }
+
+ @Override
+ public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException {
+
+ }
+
+ @Override
+ public boolean transact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void linkToDeath(DeathRecipient recipient, int flags)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
+ return false;
+ }
+ };
+ }
+ return mBinder;
+ }
+
//------------ NOT OVERRIDEN --------------------
@Override
@@ -991,6 +1157,12 @@ public final class BridgeContext extends Context {
}
@Override
+ public int checkSelfPermission(String arg0) {
+ // pass
+ return 0;
+ }
+
+ @Override
public int checkPermission(String arg0, int arg1, int arg2, IBinder arg3) {
// pass
return 0;
@@ -1370,6 +1542,11 @@ public final class BridgeContext extends Context {
// pass
}
+ public void sendBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission, int appOp) {
+ // pass
+ }
+
@Override
public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
@@ -1563,4 +1740,13 @@ public final class BridgeContext extends Context {
// pass
return new File[0];
}
+
+ public void setScrollYPos(@NonNull View view, int scrollPos) {
+ mScrollYPos.put(view, scrollPos);
+ }
+
+ public int getScrollYPos(@NonNull View view) {
+ Integer pos = mScrollYPos.get(view);
+ return pos != null ? pos : 0;
+ }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
index c44a57c..8899e53 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
@@ -164,7 +164,8 @@ public class BridgeIInputMethodManager implements IInputMethodManager {
}
@Override
- public void showInputMethodPickerFromClient(IInputMethodClient arg0) throws RemoteException {
+ public void showInputMethodPickerFromClient(IInputMethodClient arg0,
+ int arg1) throws RemoteException {
// TODO Auto-generated method stub
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
index 9b755cd..085df85 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
@@ -147,6 +147,11 @@ public class BridgePowerManager implements IPowerManager {
}
@Override
+ public boolean isDeviceIdleMode() throws RemoteException {
+ return false;
+ }
+
+ @Override
public boolean isScreenBrightnessBoosted() throws RemoteException {
return false;
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
index fb5d44f..771c3c8 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
@@ -91,7 +91,11 @@ public final class BridgeWindow implements IWindow {
}
@Override
- public void doneAnimating() {
+ public void onAnimationStarted(int remainingFrameCount) {
+ }
+
+ @Override
+ public void onAnimationStopped() {
}
@Override
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index 8575839..c92df6d 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -191,12 +191,6 @@ public final class BridgeWindowSession implements IWindowSession {
}
@Override
- public void setUniverseTransform(IBinder window, float alpha, float offx, float offy,
- float dsdx, float dtdx, float dsdy, float dtdy) {
- // pass for now.
- }
-
- @Override
public IBinder asBinder() {
// pass for now.
return null;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/DesignLibUtil.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/DesignLibUtil.java
new file mode 100644
index 0000000..0426907
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/DesignLibUtil.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.android.support;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.View;
+
+import java.lang.reflect.Method;
+
+import static com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException;
+import static com.android.layoutlib.bridge.util.ReflectionUtils.getMethod;
+import static com.android.layoutlib.bridge.util.ReflectionUtils.invoke;
+
+/**
+ * Utility class for working with the design support lib.
+ */
+public class DesignLibUtil {
+
+ private static final String PKG_PREFIX = "android.support.design.widget.";
+ public static final String CN_COORDINATOR_LAYOUT = PKG_PREFIX + "CoordinatorLayout";
+ public static final String CN_APPBAR_LAYOUT = PKG_PREFIX + "AppBarLayout";
+ public static final String CN_COLLAPSING_TOOLBAR_LAYOUT =
+ PKG_PREFIX + "CollapsingToolbarLayout";
+ public static final String CN_TOOLBAR = "android.support.v7.widget.Toolbar";
+ public static final int SCROLL_AXIS_VERTICAL = 1 << 1;
+
+ /**
+ * Tries to set the title of a view. This is used to set the title in a
+ * CollapsingToolbarLayout.
+ * <p/>
+ * Any exceptions thrown during the process are logged in {@link Bridge#getLog()}
+ */
+ public static void setTitle(@NonNull View view, @Nullable String title) {
+ if (title == null) {
+ return;
+ }
+ try {
+ Method setTitle = getMethod(view.getClass(), "setTitle", CharSequence.class);
+ if (setTitle != null) {
+ invoke(setTitle, view, title);
+ }
+ } catch (ReflectionException e) {
+ Bridge.getLog().warning(LayoutLog.TAG_INFO,
+ "Error occurred while trying to set title.", e);
+ }
+ }
+}
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 e4c7288..5e5b3c9 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
@@ -16,8 +16,6 @@
package com.android.layoutlib.bridge.android.support;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.LayoutlibCallback;
import com.android.ide.common.rendering.api.SessionParams;
@@ -25,6 +23,8 @@ import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.RenderParamsFlags;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.view.View;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
index dd1f661..8b8cc38 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
@@ -16,8 +16,6 @@
package com.android.layoutlib.bridge.bars;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams;
@@ -26,6 +24,8 @@ import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.impl.ResourceHelper;
import com.android.resources.ResourceType;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.ContextThemeWrapper;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/BridgeActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/BridgeActionBar.java
index 3d1a9b9..a19b689 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/BridgeActionBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/BridgeActionBar.java
@@ -16,7 +16,6 @@
package com.android.layoutlib.bridge.bars;
-import com.android.annotations.NonNull;
import com.android.ide.common.rendering.api.ActionBarCallback;
import com.android.ide.common.rendering.api.ActionBarCallback.HomeButtonStyle;
import com.android.ide.common.rendering.api.RenderResources;
@@ -24,6 +23,7 @@ import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.layoutlib.bridge.android.BridgeContext;
+import android.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
index bc1a41d..90a1c75 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
@@ -16,7 +16,6 @@
package com.android.layoutlib.bridge.bars;
-import com.android.annotations.NonNull;
import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.StyleResourceValue;
@@ -32,6 +31,7 @@ import com.android.resources.ResourceType;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.NonNull;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBar.java
index a1c9065..bb3d13f 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBar.java
@@ -16,8 +16,6 @@
package com.android.layoutlib.bridge.bars;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams;
@@ -27,6 +25,8 @@ import com.android.internal.view.menu.MenuItemImpl;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.impl.ResourceHelper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.DisplayMetrics;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java
index 44c2cd8..daad602 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java
@@ -16,8 +16,6 @@
package com.android.layoutlib.bridge.bars;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.ActionBarCallback;
import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.ResourceValue;
@@ -31,6 +29,8 @@ import com.android.internal.widget.DecorToolbar;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.impl.ResourceHelper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActionBar;
import android.app.ActionBar.Tab;
import android.app.ActionBar.TabListener;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
index 04aadff..dcf82a3 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
@@ -24,6 +24,7 @@ import org.xmlpull.v1.XmlPullParserException;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -32,6 +33,17 @@ public class NavigationBar extends CustomBar {
/** Navigation bar background color attribute name. */
private static final String ATTR_COLOR = "navigationBarColor";
+ // These correspond to @dimen/navigation_side_padding in the system ui code.
+ private static final int PADDING_WIDTH_DEFAULT = 36;
+ private static final int PADDING_WIDTH_SW360 = 40;
+ private static final int PADDING_WIDTH_SW400 = 50;
+ // These corresponds to @dimen/navigation_key_width in the system ui code.
+ private static final int WIDTH_DEFAULT = 36;
+ private static final int WIDTH_SW360 = 40;
+ private static final int WIDTH_SW600 = 48;
+ private static final String LAYOUT_XML = "/bars/navigation_bar.xml";
+ private static final String LAYOUT_600DP_XML = "/bars/navigation_bar600dp.xml";
+
/**
* Constructor to be used when creating the {@link NavigationBar} as a regular control.
@@ -45,13 +57,13 @@ public class NavigationBar extends CustomBar {
((BridgeContext) context).getConfiguration().getLayoutDirection() ==
View.LAYOUT_DIRECTION_RTL,
(context.getApplicationInfo().flags & ApplicationInfo.FLAG_SUPPORTS_RTL) != 0,
- context.getApplicationInfo().targetSdkVersion);
+ 0);
}
public NavigationBar(BridgeContext context, Density density, int orientation, boolean isRtl,
boolean rtlEnabled, int simulatedPlatformVersion) throws XmlPullParserException {
- super(context, orientation, "/bars/navigation_bar.xml", "navigation_bar.xml",
- simulatedPlatformVersion);
+ super(context, orientation, getShortestWidth(context)>= 600 ? LAYOUT_600DP_XML : LAYOUT_XML,
+ "navigation_bar.xml", simulatedPlatformVersion);
int color = getThemeAttrColor(ATTR_COLOR, true);
setBackgroundColor(color == 0 ? 0xFF000000 : color);
@@ -61,19 +73,76 @@ public class NavigationBar extends CustomBar {
// We do know the order though.
// 0 is a spacer.
int back = 1;
- int recent = 3;
+ int recent = 5;
if (orientation == LinearLayout.VERTICAL || (isRtl && !rtlEnabled)) {
// If RTL is enabled, then layoutlib mirrors the layout for us.
- back = 3;
+ back = 5;
recent = 1;
}
//noinspection SpellCheckingInspection
- loadIcon(back, "ic_sysbar_back.png", density, isRtl);
+ loadIcon(back, "ic_sysbar_back.png", density, isRtl);
//noinspection SpellCheckingInspection
- loadIcon(2, "ic_sysbar_home.png", density, isRtl);
+ loadIcon(3, "ic_sysbar_home.png", density, isRtl);
//noinspection SpellCheckingInspection
loadIcon(recent, "ic_sysbar_recent.png", density, isRtl);
+ setupNavBar(context, orientation);
+ }
+
+ private void setupNavBar(BridgeContext context, int orientation) {
+ float sw = getShortestWidth(context);
+ View leftPadding = getChildAt(0);
+ View rightPadding = getChildAt(6);
+ setSize(context, leftPadding, orientation, getSidePadding(sw));
+ setSize(context, rightPadding, orientation, getSidePadding(sw));
+ int navButtonWidth = getWidth(sw);
+ for (int i = 1; i < 6; i += 2) {
+ View navButton = getChildAt(i);
+ setSize(context, navButton, orientation, navButtonWidth);
+ }
+ if (sw >= 600) {
+ setSize(context, getChildAt(2), orientation, 128);
+ setSize(context, getChildAt(4), orientation, 128);
+ }
+ }
+
+ private static void setSize(BridgeContext context, View view, int orientation, int size) {
+ size *= context.getMetrics().density;
+ LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
+ if (orientation == HORIZONTAL) {
+ layoutParams.width = size;
+ } else {
+ layoutParams.height = size;
+ }
+ view.setLayoutParams(layoutParams);
+ }
+
+ private static int getSidePadding(float sw) {
+ if (sw >= 400) {
+ return PADDING_WIDTH_SW400;
+ }
+ if (sw >= 360) {
+ return PADDING_WIDTH_SW360;
+ }
+ return PADDING_WIDTH_DEFAULT;
+ }
+
+ private static int getWidth(float sw) {
+ if (sw >= 600) {
+ return WIDTH_SW600;
+ }
+ if (sw >= 360) {
+ return WIDTH_SW360;
+ }
+ return WIDTH_DEFAULT;
+ }
+
+ private static float getShortestWidth(BridgeContext context) {
+ DisplayMetrics metrics = context.getMetrics();
+ float sw = metrics.widthPixels < metrics.heightPixels ?
+ metrics.widthPixels : metrics.heightPixels;
+ sw /= metrics.density;
+ return sw;
}
@Override
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 261cc98..47258b6 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
@@ -19,6 +19,7 @@ package com.android.layoutlib.bridge.impl;
import com.android.layoutlib.bridge.util.Debug;
import com.android.layoutlib.bridge.util.SparseWeakArray;
+import android.annotation.Nullable;
import android.util.SparseArray;
import java.lang.ref.WeakReference;
@@ -48,7 +49,7 @@ import java.util.List;
* int -> Delegate class link.
*
* Native methods usually always have the int as parameters. The first thing the delegate method
- * will do is call {@link #getDelegate(int)} to get the Java object matching the int.
+ * will do is call {@link #getDelegate(long)} to get the Java object matching the int.
*
* Typical native init methods are returning a new int back to the Java class, so
* {@link #addNewDelegate(Object)} does the same.
@@ -57,7 +58,7 @@ import java.util.List;
* the Java object needs to count as a reference (even though it only holds an int), we use the
* following mechanism:
*
- * - {@link #addNewDelegate(Object)} and {@link #removeJavaReferenceFor(int)} adds and removes
+ * - {@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.
*
@@ -70,12 +71,13 @@ import java.util.List;
* @param <T> the delegate class to manage
*/
public final class DelegateManager<T> {
+ @SuppressWarnings("FieldCanBeLocal")
private final Class<T> mClass;
private final SparseWeakArray<T> mDelegates = new SparseWeakArray<T>();
/** list used to store delegates when their main object holds a reference to them.
* This is to ensure that the WeakReference in the SparseWeakArray doesn't get GC'ed
* @see #addNewDelegate(Object)
- * @see #removeJavaReferenceFor(int)
+ * @see #removeJavaReferenceFor(long)
*/
private final List<T> mJavaReferences = new ArrayList<T>();
private int mDelegateCounter = 0;
@@ -94,6 +96,7 @@ public final class DelegateManager<T> {
* @param native_object the native int.
* @return the delegate or null if not found.
*/
+ @Nullable
public T getDelegate(long native_object) {
if (native_object > 0) {
T delegate = mDelegates.get(native_object);
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 803849f..6e67f59 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
@@ -17,10 +17,12 @@
package com.android.layoutlib.bridge.impl;
-import org.kxml2.io.KXmlParser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
@@ -35,24 +37,36 @@ import java.io.InputStream;
*/
public class ParserFactory {
+ public final static boolean LOG_PARSER = false;
+
private final static String ENCODING = "UTF-8"; //$NON-NLS-1$
- public final static boolean LOG_PARSER = false;
+ // Used to get a new XmlPullParser from the client.
+ @Nullable
+ private static com.android.ide.common.rendering.api.ParserFactory sParserFactory;
- public static XmlPullParser create(File f)
+ public static void setParserFactory(
+ @Nullable com.android.ide.common.rendering.api.ParserFactory parserFactory) {
+ sParserFactory = parserFactory;
+ }
+
+ @NonNull
+ public static XmlPullParser create(@NonNull File f)
throws XmlPullParserException, FileNotFoundException {
InputStream stream = new FileInputStream(f);
return create(stream, f.getName(), f.length());
}
- public static XmlPullParser create(InputStream stream, String name)
+ @NonNull
+ public static XmlPullParser create(@NonNull InputStream stream, @Nullable String name)
throws XmlPullParserException {
return create(stream, name, -1);
}
- private static XmlPullParser create(InputStream stream, String name, long size)
- throws XmlPullParserException {
- KXmlParser parser = instantiateParser(name);
+ @NonNull
+ private static XmlPullParser create(@NonNull InputStream stream, @Nullable String name,
+ long size) throws XmlPullParserException {
+ XmlPullParser parser = instantiateParser(name);
stream = readAndClose(stream, name, size);
@@ -60,19 +74,20 @@ public class ParserFactory {
return parser;
}
- private static KXmlParser instantiateParser(String name) throws XmlPullParserException {
- KXmlParser parser;
- if (name != null) {
- parser = new CustomParser(name);
- } else {
- parser = new KXmlParser();
+ @NonNull
+ public static XmlPullParser instantiateParser(@Nullable String name)
+ throws XmlPullParserException {
+ if (sParserFactory == null) {
+ throw new XmlPullParserException("ParserFactory not initialized.");
}
+ XmlPullParser parser = sParserFactory.createParser(name);
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
return parser;
}
- private static InputStream readAndClose(InputStream stream, String name, long size)
- throws XmlPullParserException {
+ @NonNull
+ private static InputStream readAndClose(@NonNull InputStream stream, @Nullable String name,
+ long size) throws XmlPullParserException {
// just a sanity check. It's doubtful we'll have such big files!
if (size > Integer.MAX_VALUE) {
throw new XmlPullParserException("File " + name + " is too big to be parsed");
@@ -121,22 +136,8 @@ public class ParserFactory {
} finally {
try {
bufferedStream.close();
- } catch (IOException e) {
+ } catch (IOException ignored) {
}
}
}
-
- private static class CustomParser extends KXmlParser {
- private final String mName;
-
- CustomParser(String name) {
- super();
- mName = name;
- }
-
- @Override
- public String toString() {
- return mName;
- }
- }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
index c708316..de77d57 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
@@ -227,6 +227,9 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso
* The counterpart is {@link #tearDown()}.
*/
private void setUp() {
+ // setup the ParserFactory
+ ParserFactory.setParserFactory(mParams.getLayoutlibCallback().getParserFactory());
+
// make sure the Resources object references the context (and other objects) for this
// scene
mContext.initResources();
@@ -271,7 +274,7 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso
mContext.getRenderResources().setFrameworkResourceIdProvider(null);
mContext.getRenderResources().setLogger(null);
}
-
+ ParserFactory.setParserFactory(null);
}
public static BridgeContext getCurrentContext() {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java
index 9e26502..26f9000 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java
@@ -38,6 +38,9 @@ import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
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 f6e5ef1..d571d35 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
@@ -16,8 +16,6 @@
package com.android.layoutlib.bridge.impl;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.AdapterBinding;
import com.android.ide.common.rendering.api.HardwareConfig;
import com.android.ide.common.rendering.api.IAnimationListener;
@@ -46,6 +44,7 @@ import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
import com.android.layoutlib.bridge.android.RenderParamsFlags;
+import com.android.layoutlib.bridge.android.support.DesignLibUtil;
import com.android.layoutlib.bridge.android.support.RecyclerViewUtil;
import com.android.layoutlib.bridge.bars.AppCompatActionBar;
import com.android.layoutlib.bridge.bars.BridgeActionBar;
@@ -68,6 +67,8 @@ import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.LayoutTransition;
import android.animation.LayoutTransition.TransitionListener;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Fragment_Delegate;
import android.graphics.Bitmap;
import android.graphics.Bitmap_Delegate;
@@ -148,7 +149,6 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
private int mTitleBarSize;
private int mActionBarSize;
-
// information being returned through the API
private BufferedImage mImage;
private List<ViewInfo> mViewInfoList;
@@ -424,6 +424,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
// post-inflate process. For now this supports TabHost/TabWidget
postInflateProcess(view, params.getLayoutlibCallback(), isPreference ? view : null);
+ setActiveToolbar(view, context, params);
+
// get the background drawable
if (mWindowBackground != null) {
Drawable d = ResourceHelper.getDrawable(mWindowBackground, context);
@@ -544,6 +546,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
// now do the layout.
mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
+ handleScrolling(mViewRoot);
+
if (params.isLayoutOnly()) {
// delete the canvas and image to reset them on the next full rendering
mImage = null;
@@ -1350,6 +1354,99 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
}
/**
+ * If the root layout is a CoordinatorLayout with an AppBar:
+ * Set the title of the AppBar to the title of the activity context.
+ */
+ private void setActiveToolbar(View view, BridgeContext context, SessionParams params) {
+ View coordinatorLayout = findChildView(view, DesignLibUtil.CN_COORDINATOR_LAYOUT);
+ if (coordinatorLayout == null) {
+ return;
+ }
+ View appBar = findChildView(coordinatorLayout, DesignLibUtil.CN_APPBAR_LAYOUT);
+ if (appBar == null) {
+ return;
+ }
+ ViewGroup collapsingToolbar =
+ (ViewGroup) findChildView(appBar, DesignLibUtil.CN_COLLAPSING_TOOLBAR_LAYOUT);
+ if (collapsingToolbar == null) {
+ return;
+ }
+ if (!hasToolbar(collapsingToolbar)) {
+ return;
+ }
+ RenderResources res = context.getRenderResources();
+ String title = params.getAppLabel();
+ ResourceValue titleValue = res.findResValue(title, false);
+ if (titleValue != null && titleValue.getValue() != null) {
+ title = titleValue.getValue();
+ }
+ DesignLibUtil.setTitle(collapsingToolbar, title);
+ }
+
+ private View findChildView(View view, String className) {
+ if (!(view instanceof ViewGroup)) {
+ return null;
+ }
+ ViewGroup group = (ViewGroup) view;
+ for (int i = 0; i < group.getChildCount(); i++) {
+ if (isInstanceOf(group.getChildAt(i), className)) {
+ return group.getChildAt(i);
+ }
+ }
+ return null;
+ }
+
+ private boolean hasToolbar(View collapsingToolbar) {
+ if (!(collapsingToolbar instanceof ViewGroup)) {
+ return false;
+ }
+ ViewGroup group = (ViewGroup) collapsingToolbar;
+ for (int i = 0; i < group.getChildCount(); i++) {
+ if (isInstanceOf(group.getChildAt(i), DesignLibUtil.CN_TOOLBAR)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Set the vertical scroll position on all the components with the "scrollY" attribute. If the
+ * component supports nested scrolling attempt that first, then use the unconsumed scroll part
+ * to scroll the content in the component.
+ */
+ private void handleScrolling(View view) {
+ BridgeContext context = getContext();
+ int scrollPos = context.getScrollYPos(view);
+ if (scrollPos != 0) {
+ if (view.isNestedScrollingEnabled()) {
+ int[] consumed = new int[2];
+ if (view.startNestedScroll(DesignLibUtil.SCROLL_AXIS_VERTICAL)) {
+ view.dispatchNestedPreScroll(0, scrollPos, consumed, null);
+ view.dispatchNestedScroll(consumed[0], consumed[1], 0, scrollPos, null);
+ view.stopNestedScroll();
+ scrollPos -= consumed[1];
+ }
+ }
+ if (scrollPos != 0) {
+ view.scrollBy(0, scrollPos);
+ } else {
+ view.scrollBy(0, scrollPos);
+ }
+ } else {
+ view.scrollBy(0, scrollPos);
+ }
+
+ if (!(view instanceof ViewGroup)) {
+ return;
+ }
+ ViewGroup group = (ViewGroup) view;
+ for (int i = 0; i < group.getChildCount(); i++) {
+ View child = group.getChildAt(i);
+ handleScrolling(child);
+ }
+ }
+
+ /**
* Check if the object is an instance of a class named {@code className}. This doesn't work
* for interfaces.
*/
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 677c744..ca77193 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
@@ -16,7 +16,7 @@
package com.android.layoutlib.bridge.impl;
-import com.android.annotations.NonNull;
+import com.android.SdkConstants;
import com.android.ide.common.rendering.api.DensityBasedResourceValue;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.RenderResources;
@@ -31,6 +31,7 @@ import com.android.resources.Density;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.NonNull;
import android.content.res.ColorStateList;
import android.content.res.Resources.Theme;
import android.graphics.Bitmap;
@@ -70,6 +71,10 @@ public final class ResourceHelper {
public static int getColor(String value) {
if (value != null) {
if (!value.startsWith("#")) {
+ if (value.startsWith(SdkConstants.PREFIX_THEME_REF)) {
+ throw new NumberFormatException(String.format(
+ "Attribute '%s' not found. Are you using the right theme?", value));
+ }
throw new NumberFormatException(
String.format("Color value '%s' must start with #", value));
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java
index 979aa33..08a8faf 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java
@@ -16,10 +16,10 @@
package com.android.layoutlib.bridge.util;
-import com.android.annotations.NonNull;
import com.android.resources.ResourceType;
import com.android.util.Pair;
+import android.annotation.NonNull;
import android.util.SparseArray;
import java.util.HashMap;
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 8e61edf..b324451 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
@@ -16,8 +16,8 @@
package com.android.layoutlib.bridge.util;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
diff --git a/tools/layoutlib/bridge/src/libcore/icu/DateIntervalFormat_Delegate.java b/tools/layoutlib/bridge/src/libcore/icu/DateIntervalFormat_Delegate.java
deleted file mode 100644
index d94c205..0000000
--- a/tools/layoutlib/bridge/src/libcore/icu/DateIntervalFormat_Delegate.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.icu;
-
-import java.text.FieldPosition;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-import com.ibm.icu.text.DateIntervalFormat;
-import com.ibm.icu.util.DateInterval;
-import com.ibm.icu.util.TimeZone;
-import com.ibm.icu.util.ULocale;
-
-public class DateIntervalFormat_Delegate {
-
- // ---- delegate manager ----
- private static final DelegateManager<DateIntervalFormat_Delegate> sManager =
- new DelegateManager<DateIntervalFormat_Delegate>(DateIntervalFormat_Delegate.class);
-
- // ---- delegate data ----
- private DateIntervalFormat mFormat;
-
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/static String formatDateInterval(long address, long fromDate, long toDate) {
- DateIntervalFormat_Delegate delegate = sManager.getDelegate((int)address);
- if (delegate == null) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Unable for find native DateIntervalFormat", null);
- return null;
- }
- DateInterval interval = new DateInterval(fromDate, toDate);
- StringBuffer sb = new StringBuffer();
- FieldPosition pos = new FieldPosition(0);
- delegate.mFormat.format(interval, sb, pos);
- return sb.toString();
- }
-
- @LayoutlibDelegate
- /*package*/ static long createDateIntervalFormat(String skeleton, String localeName,
- String tzName) {
- TimeZone prevDefaultTz = TimeZone.getDefault();
- TimeZone.setDefault(TimeZone.getTimeZone(tzName));
- DateIntervalFormat_Delegate newDelegate = new DateIntervalFormat_Delegate();
- newDelegate.mFormat =
- DateIntervalFormat.getInstance(skeleton, new ULocale(localeName));
- TimeZone.setDefault(prevDefaultTz);
- return sManager.addNewDelegate(newDelegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static void destroyDateIntervalFormat(long address) {
- sManager.removeJavaReferenceFor((int)address);
- }
-
-}
diff --git a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
index b8b5fed..9c58010 100644
--- a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
+++ b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
@@ -17,9 +17,11 @@
package libcore.icu;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-import com.ibm.icu.text.DateTimePatternGenerator;
-import com.ibm.icu.util.Currency;
-import com.ibm.icu.util.ULocale;
+
+import android.icu.text.DateTimePatternGenerator;
+import android.icu.util.Currency;
+import android.icu.util.ULocale;
+import android.icu.util.VersionInfo;
import java.util.Locale;
@@ -53,18 +55,19 @@ public class ICU_Delegate {
}
@LayoutlibDelegate
+ @SuppressWarnings("deprecation")
/*package*/ static String getCldrVersion() {
- return "22.1.1"; // TODO: check what the right value should be.
+ return VersionInfo.ICU_DATA_VERSION.toString();
}
@LayoutlibDelegate
/*package*/ static String getIcuVersion() {
- return "unknown_layoutlib";
+ return VersionInfo.ICU_VERSION.toString();
}
@LayoutlibDelegate
/*package*/ static String getUnicodeVersion() {
- return "5.2";
+ return VersionInfo.UNICODE_7_0.toString();
}
@LayoutlibDelegate
@@ -181,8 +184,8 @@ public class ICU_Delegate {
/*package*/ static boolean initLocaleDataNative(String locale, LocaleData result) {
// Used by Calendar.
- result.firstDayOfWeek = Integer.valueOf(1);
- result.minimalDaysInFirstWeek = Integer.valueOf(1);
+ result.firstDayOfWeek = 1;
+ result.minimalDaysInFirstWeek = 1;
// Used by DateFormatSymbols.
result.amPm = new String[] { "AM", "PM" };
@@ -252,4 +255,9 @@ public class ICU_Delegate {
/*package*/ static String getDefaultLocale() {
return ICU.getDefaultLocale();
}
+
+ @LayoutlibDelegate
+ /*package*/ static String getTZDataVersion() {
+ return ICU.getTZDataVersion();
+ }
}
diff --git a/tools/layoutlib/bridge/tests/Android.mk b/tools/layoutlib/bridge/tests/Android.mk
index 11390c3..5eef24a 100644
--- a/tools/layoutlib/bridge/tests/Android.mk
+++ b/tools/layoutlib/bridge/tests/Android.mk
@@ -26,7 +26,6 @@ LOCAL_MODULE_TAGS := optional
LOCAL_JAVA_LIBRARIES := layoutlib \
kxml2-2.3.0 \
- icu4j \
layoutlib_api-prebuilt \
tools-common-prebuilt \
sdk-common \
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle
index 0f37fce..4561e1b 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle
@@ -3,7 +3,7 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.1.3'
+ classpath 'com.android.tools.build:gradle:1.2.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -19,12 +19,12 @@ allprojects {
apply plugin: 'com.android.application'
android {
- compileSdkVersion 21
+ compileSdkVersion 22
buildToolsVersion '21.1.2'
defaultConfig {
applicationId 'com.android.layoutlib.test.myapplication'
- minSdkVersion 19
- targetSdkVersion 21
+ minSdkVersion 21
+ targetSdkVersion 22
versionCode 1
versionName '1.0'
}
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
new file mode 100644
index 0000000..e0373cb
--- /dev/null
+++ 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/MyActivity.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class
index 8af93eb..ec42017 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$array.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$array.class
new file mode 100644
index 0000000..b87f193
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$array.class
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class
index 9bab801..e2968d4 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.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 7ad8605..0e208f2 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 e9e0a33..2b77af3 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 069f9f7..fd01b44 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
new file mode 100644
index 0000000..91cf5b6
--- /dev/null
+++ 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 36e2688..e172b2d 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 ca438ad..aecbff6 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 a98abf5..fc3f236 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 7d8cc84..83ad35b 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 7e6113b..d5b81c4 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/allwidgets.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
index c9b76be..2b86bfb 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/array_check.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/array_check.png
new file mode 100644
index 0000000..9a13568
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/array_check.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
new file mode 100644
index 0000000..41d75de
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/ArraysCheckWidget.java
@@ -0,0 +1,41 @@
+package com.android.layoutlib.test.myapplication;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * A widget to test obtaining arrays from resources.
+ */
+public class ArraysCheckWidget extends LinearLayout {
+ public ArraysCheckWidget(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ArraysCheckWidget(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ArraysCheckWidget(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ Resources resources = context.getResources();
+ for (CharSequence chars : resources.getTextArray(R.array.array)) {
+ addTextView(context, chars);
+ }
+ for (int i : resources.getIntArray(R.array.int_array)) {
+ addTextView(context, String.valueOf(i));
+ }
+ for (String string : resources.getStringArray(R.array.string_array)) {
+ addTextView(context, string);
+ }
+ }
+
+ private void addTextView(Context context, CharSequence string) {
+ TextView textView = new TextView(context);
+ textView.setText(string);
+ textView.setTextSize(30);
+ addView(textView);
+ }
+}
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
new file mode 100644
index 0000000..50646ab
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/array_check.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<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">
+
+</com.android.layoutlib.test.myapplication.ArraysCheckWidget> \ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml
new file mode 100644
index 0000000..f6e14d2
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <array name="array">
+ <item>first</item>
+ <item>second</item>
+ </array>
+ <integer-array name="int_array">
+ <item>1</item>
+ <item>0xaB</item> <!-- hex entry (decimal 171) -->
+ <item>010</item> <!-- octal entry -->
+ <item>@integer/ten</item> <!-- value reference -->
+ <item>?attr/myattr</item> <!-- theme attr reference -->
+ </integer-array>
+ <string-array name="string_array">
+ <item>mystring</item>
+ <item>@string/hello_world</item> <!-- string ref in appNs -->
+ <!-- theme ref in android NS. value = @string/candidates_style = <u>candidates</u> -->
+ <item>?android:attr/candidatesTextStyleSpans</item>
+ <item>@android:string/unknownName</item> <!-- value = Unknown -->
+ </string-array>
+
+ <!-- resources that the above array can refer to -->
+ <integer name="ten">10</integer>
+ <integer name="twelve">12</integer>
+</resources> \ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/attrs.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..894e18c
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/attrs.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <attr name="myattr" format="integer" />
+</resources> \ No newline at end of file
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 ff6c9d2..88c9cbc 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
@@ -1,7 +1,8 @@
<resources>
<!-- Base application theme. -->
- <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+ <style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
+ <item name="myattr">@integer/ten</item>
<!-- Customize your theme here. -->
</style>
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java
index 8b362ec..d8937f4 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java
@@ -192,12 +192,12 @@ public class TestDelegates extends TestCase {
StringBuilder sb = new StringBuilder(method.getName() + "(");
for (int j = 0; j < parameters.length; j++) {
Class<?> theClass = parameters[j];
- sb.append(theClass.getName());
int dimensions = 0;
while (theClass.isArray()) {
dimensions++;
theClass = theClass.getComponentType();
}
+ sb.append(theClass.getName());
for (int i = 0; i < dimensions; i++) {
sb.append("[]");
}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java
index 92fcf90..77c997b 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java
@@ -18,12 +18,26 @@ package com.android.layoutlib.bridge.android;
import com.android.layoutlib.bridge.impl.ParserFactory;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.kxml2.io.KXmlParser;
import org.w3c.dom.Node;
import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
-import junit.framework.TestCase;
+import android.annotation.NonNull;
-public class BridgeXmlBlockParserTest extends TestCase {
+import static org.junit.Assert.assertEquals;
+
+public class BridgeXmlBlockParserTest {
+
+ @BeforeClass
+ public static void setUp() {
+ ParserFactory.setParserFactory(new ParserFactoryImpl());
+ }
+
+ @Test
public void testXmlBlockParser() throws Exception {
XmlPullParser parser = ParserFactory.create(
@@ -65,7 +79,7 @@ public class BridgeXmlBlockParserTest extends TestCase {
//------------
/**
- * Quick'n'dirty debug helper that dumps an XML structure to stdout.
+ * Quick 'n' dirty debug helper that dumps an XML structure to stdout.
*/
@SuppressWarnings("unused")
private void dump(Node node, String prefix) {
@@ -104,7 +118,20 @@ public class BridgeXmlBlockParserTest extends TestCase {
if (n != null) {
dump(n, prefix);
}
+ }
+ @AfterClass
+ public static void tearDown() {
+ ParserFactory.setParserFactory(null);
}
+ private static class ParserFactoryImpl
+ extends com.android.ide.common.rendering.api.ParserFactory {
+
+ @NonNull
+ @Override
+ public XmlPullParser createParser(String displayName) throws XmlPullParserException {
+ return new KXmlParser();
+ }
+ }
}
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 d7e5486..16911bd 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
@@ -16,7 +16,7 @@
package com.android.layoutlib.bridge.intensive;
-import com.android.annotations.NonNull;
+import android.annotation.NonNull;
import java.awt.AlphaComposite;
import java.awt.Color;
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 f2a039e..272a2b8 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
@@ -16,7 +16,6 @@
package com.android.layoutlib.bridge.intensive;
-import com.android.annotations.NonNull;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.Result;
@@ -34,9 +33,13 @@ import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback;
import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
import com.android.utils.ILogger;
-import org.junit.Before;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
import org.junit.Test;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
@@ -81,11 +84,11 @@ public class Main {
/** 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 LayoutLog mLayoutLibLog;
- private FrameworkResources mFrameworkRepo;
- private ResourceRepository mProjectResources;
- private ILogger mLogger;
- private Bridge mBridge;
+ private static LayoutLog sLayoutLibLog;
+ private static FrameworkResources sFrameworkRepo;
+ private static ResourceRepository sProjectResources;
+ private static ILogger sLogger;
+ private static Bridge sBridge;
static {
// Test that System Properties are properly set.
@@ -122,9 +125,14 @@ public class Main {
if (platformDir != null) {
return platformDir;
}
- // Test if workingDir is platform/frameworks/base/tools/layoutlib. That is, root should be
- // workingDir/../../../../ (4 levels up)
+
+ // Test if workingDir is platform/frameworks/base/tools/layoutlib/bridge.
File currentDir = workingDir;
+ if (currentDir.getName().equalsIgnoreCase("bridge")) {
+ currentDir = currentDir.getParentFile();
+ }
+ // Test if currentDir is platform/frameworks/base/tools/layoutlib. That is, root should be
+ // workingDir/../../../../ (4 levels up)
for (int i = 0; i < 4; i++) {
if (currentDir != null) {
currentDir = currentDir.getParentFile();
@@ -249,15 +257,15 @@ public class Main {
/**
* Initialize the bridge and the resource maps.
*/
- @Before
- public void setUp() {
+ @BeforeClass
+ public static void setUp() {
File data_dir = new File(PLATFORM_DIR, "data");
File res = new File(data_dir, "res");
- mFrameworkRepo = new FrameworkResources(new FolderWrapper(res));
- mFrameworkRepo.loadResources();
- mFrameworkRepo.loadPublicResources(getLogger());
+ sFrameworkRepo = new FrameworkResources(new FolderWrapper(res));
+ sFrameworkRepo.loadResources();
+ sFrameworkRepo.loadPublicResources(getLogger());
- mProjectResources =
+ sProjectResources =
new ResourceRepository(new FolderWrapper(TEST_RES_DIR + APP_TEST_RES), false) {
@NonNull
@Override
@@ -265,13 +273,13 @@ public class Main {
return new ResourceItem(name);
}
};
- mProjectResources.loadResources();
+ sProjectResources.loadResources();
File fontLocation = new File(data_dir, "fonts");
File buildProp = new File(PLATFORM_DIR, "build.prop");
File attrs = new File(res, "values" + File.separator + "attrs.xml");
- mBridge = new Bridge();
- mBridge.init(ConfigGenerator.loadProperties(buildProp), fontLocation,
+ sBridge = new Bridge();
+ sBridge.init(ConfigGenerator.loadProperties(buildProp), fontLocation,
ConfigGenerator.getEnumMap(attrs), getLayoutLog());
}
@@ -288,6 +296,20 @@ public class Main {
renderAndVerify("allwidgets.xml", "allwidgets.png");
}
+ @Test
+ public void testArrayCheck() throws ClassNotFoundException {
+ renderAndVerify("array_check.xml", "array_check.png");
+ }
+
+ @AfterClass
+ public static void tearDown() {
+ sLayoutLibLog = null;
+ sFrameworkRepo = null;
+ sProjectResources = null;
+ sLogger = null;
+ sBridge = null;
+ }
+
/**
* Create a new rendering session and test that rendering given layout on nexus 5
* doesn't throw any exceptions and matches the provided image.
@@ -302,7 +324,7 @@ public class Main {
// TODO: Set up action bar handler properly to test menu rendering.
// Create session params.
SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, layoutLibCallback);
- RenderSession session = mBridge.createSession(params);
+ RenderSession session = sBridge.createSession(params);
if (!session.getResult().isSuccess()) {
getLogger().error(session.getResult().getException(),
session.getResult().getErrorMessage());
@@ -328,9 +350,9 @@ public class Main {
ConfigGenerator configGenerator, LayoutLibTestCallback layoutLibCallback) {
FolderConfiguration config = configGenerator.getFolderConfig();
ResourceResolver resourceResolver =
- ResourceResolver.create(mProjectResources.getConfiguredResources(config),
- mFrameworkRepo.getConfiguredResources(config),
- "Theme.Material.Light.DarkActionBar", false);
+ ResourceResolver.create(sProjectResources.getConfiguredResources(config),
+ sFrameworkRepo.getConfiguredResources(config),
+ "AppTheme", true);
return new SessionParams(
layoutParser,
@@ -344,9 +366,9 @@ public class Main {
getLayoutLog());
}
- private LayoutLog getLayoutLog() {
- if (mLayoutLibLog == null) {
- mLayoutLibLog = new LayoutLog() {
+ private static LayoutLog getLayoutLog() {
+ if (sLayoutLibLog == null) {
+ sLayoutLibLog = new LayoutLog() {
@Override
public void warning(String tag, String message, Object data) {
System.out.println("Warning " + tag + ": " + message);
@@ -354,13 +376,14 @@ public class Main {
}
@Override
- public void fidelityWarning(String tag, String message, Throwable throwable,
- Object data) {
+ public void fidelityWarning(@Nullable String tag, String message,
+ Throwable throwable, Object data) {
+
System.out.println("FidelityWarning " + tag + ": " + message);
if (throwable != null) {
throwable.printStackTrace();
}
- failWithMsg(message);
+ failWithMsg(message == null ? "" : message);
}
@Override
@@ -379,18 +402,18 @@ public class Main {
}
};
}
- return mLayoutLibLog;
+ return sLayoutLibLog;
}
- private ILogger getLogger() {
- if (mLogger == null) {
- mLogger = new ILogger() {
+ private static ILogger getLogger() {
+ if (sLogger == null) {
+ sLogger = new ILogger() {
@Override
- public void error(Throwable t, String msgFormat, Object... args) {
+ public void error(Throwable t, @Nullable String msgFormat, Object... args) {
if (t != null) {
t.printStackTrace();
}
- failWithMsg(msgFormat, args);
+ failWithMsg(msgFormat == null ? "" : msgFormat, args);
}
@Override
@@ -409,7 +432,7 @@ public class Main {
}
};
}
- return mLogger;
+ return sLogger;
}
private static void failWithMsg(@NonNull String msgFormat, Object... args) {
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java
index 1191df6..8964c45 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java
@@ -111,6 +111,21 @@ public class ConfigGenerator {
.setSoftButtons(true)
.setNavigation(Navigation.NONAV);
+ public static final ConfigGenerator NEXUS_5_LAND = new ConfigGenerator()
+ .setScreenHeight(1080)
+ .setScreenWidth(1920)
+ .setXdpi(445)
+ .setYdpi(445)
+ .setOrientation(ScreenOrientation.LANDSCAPE)
+ .setDensity(Density.XXHIGH)
+ .setRatio(ScreenRatio.NOTLONG)
+ .setSize(ScreenSize.NORMAL)
+ .setKeyboard(Keyboard.NOKEY)
+ .setTouchScreen(TouchScreen.FINGER)
+ .setKeyboardState(KeyboardState.SOFT)
+ .setSoftButtons(true)
+ .setNavigation(Navigation.NONAV);
+
private static final String TAG_ATTR = "attr";
private static final String TAG_ENUM = "enum";
private static final String TAG_FLAG = "flag";
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 5b648ef..6c16ed0 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
@@ -21,6 +21,7 @@ import com.android.ide.common.rendering.api.ActionBarCallback;
import com.android.ide.common.rendering.api.AdapterBinding;
import com.android.ide.common.rendering.api.ILayoutPullParser;
import com.android.ide.common.rendering.api.LayoutlibCallback;
+import com.android.ide.common.rendering.api.ParserFactory;
import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.IntArrayWrapper;
@@ -28,6 +29,13 @@ import com.android.resources.ResourceType;
import com.android.util.Pair;
import com.android.utils.ILogger;
+import org.kxml2.io.KXmlParser;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -155,4 +163,17 @@ public class LayoutLibTestCallback extends LayoutlibCallback {
public boolean supports(int ideFeature) {
return false;
}
+
+ @NonNull
+ @Override
+ public ParserFactory getParserFactory() {
+ return new ParserFactory() {
+ @NonNull
+ @Override
+ public XmlPullParser createParser(@Nullable String debugName)
+ throws XmlPullParserException {
+ return new KXmlParser();
+ }
+ };
+ }
}
diff --git a/tools/layoutlib/bridge/update_nav_icons.sh b/tools/layoutlib/bridge/update_nav_icons.sh
new file mode 100755
index 0000000..7030d19
--- /dev/null
+++ b/tools/layoutlib/bridge/update_nav_icons.sh
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+# copies the navigation bar icons from system ui code to layoutlib.
+# to run, simply execute the script. (if not using bash, cd to the dir
+# containing this script and then run by ./update_nav_icons.sh)
+
+# Try to get the location of this script.
+if [ -n $BASH ]; then
+ # see http://stackoverflow.com/a/246128/1546000
+ MY_LOCATION=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+ cd $MY_LOCATION
+else
+ # Let's assume script was run from the same dir.
+ MY_LOCATION=$(pwd)
+fi
+
+# Check mac or linux to get sed argument to enable extended regex.
+case $(uname -s) in
+ Darwin)
+ EXT_REGEX="-E"
+ ;;
+ *)
+ EXT_REGEX="-r"
+ ;;
+esac
+
+
+FB="frameworks/base"
+# frameworks/base relative to current location
+FB=$(echo $MY_LOCATION | sed $EXT_REGEX -e "s,.*$FB[^/]*/,," -e "s,[^/]+,..,g")
+CURRENT_API=21 # update only if icons change from this api version.
+DENSITIES="ldpi mdpi hdpi xhdpi xxhdpi"
+ICONS="ic_sysbar_back.png ic_sysbar_home.png ic_sysbar_recent.png"
+BARS="./resources/bars/"
+
+for icon in $ICONS
+do
+ for density in $DENSITIES
+ do
+ destination="$BARS/v$CURRENT_API/$density/"
+ mkdir -p "$destination" # create if not present.
+ cp -v "$FB/packages/SystemUI/res/drawable-$density/$icon" "$destination"
+ done
+
+ for density in $DENSITIES
+ do
+ destination="$BARS/v$CURRENT_API/ldrtl-$density/"
+ mkdir -p "$destination"
+ cp -v "$FB/packages/SystemUI/res/drawable-ldrtl-$density/$icon" "$destination"
+ done
+done
diff --git a/tools/layoutlib/create/create.iml b/tools/layoutlib/create/create.iml
index b7e8eb3..9b18e73 100644
--- a/tools/layoutlib/create/create.iml
+++ b/tools/layoutlib/create/create.iml
@@ -11,8 +11,17 @@
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="library" name="asm-4.0" level="project" />
+ <orderEntry type="module-library">
+ <library name="asm-4.0">
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/asm/asm-4.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/asm/src.zip!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
<orderEntry type="library" scope="TEST" name="JUnit4" level="application" />
</component>
-</module>
-
+</module> \ No newline at end of file
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
index aa51c46..c8b2b84 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
@@ -728,7 +728,7 @@ public class AsmAnalyzer {
// Check if method needs to replaced by a call to a different method.
- if (ReplaceMethodCallsAdapter.isReplacementNeeded(owner, name, desc)) {
+ if (ReplaceMethodCallsAdapter.isReplacementNeeded(owner, name, desc, mOwnerClass)) {
mReplaceMethodCallClasses.add(mOwnerClass);
}
}
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 bd6f070..f6c2626 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
@@ -24,9 +24,11 @@ import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@@ -72,6 +74,9 @@ public class AsmGenerator {
/** FQCN Names of classes to refactor. All reference to old-FQCN will be updated to new-FQCN.
* map old-FQCN => new-FQCN */
private final HashMap<String, String> mRefactorClasses;
+ /** Methods to inject. FQCN of class in which method should be injected => runnable that does
+ * the injection. */
+ private final Map<String, ICreateInfo.InjectMethodRunnable> mInjectedMethodsMap;
/**
* Creates a new generator that can generate the output JAR with the stubbed classes.
@@ -83,7 +88,23 @@ public class AsmGenerator {
public AsmGenerator(Log log, String osDestJar, ICreateInfo createInfo) {
mLog = log;
mOsDestJar = osDestJar;
- mInjectClasses = createInfo.getInjectedClasses();
+ ArrayList<Class<?>> injectedClasses =
+ new ArrayList<Class<?>>(Arrays.asList(createInfo.getInjectedClasses()));
+ // Search for and add anonymous inner classes also.
+ ListIterator<Class<?>> iter = injectedClasses.listIterator();
+ while (iter.hasNext()) {
+ Class<?> clazz = iter.next();
+ try {
+ int i = 1;
+ while(i < 100) {
+ iter.add(Class.forName(clazz.getName() + "$" + i));
+ i++;
+ }
+ } catch (ClassNotFoundException ignored) {
+ // Expected.
+ }
+ }
+ mInjectClasses = injectedClasses.toArray(new Class<?>[0]);
mStubMethods = new HashSet<String>(Arrays.asList(createInfo.getOverriddenMethods()));
// Create the map/set of methods to change to delegates
@@ -165,6 +186,8 @@ public class AsmGenerator {
}
returnTypes.add(binaryToInternalClassName(className));
}
+
+ mInjectedMethodsMap = createInfo.getInjectedMethodsMap();
}
/**
@@ -285,13 +308,7 @@ public class AsmGenerator {
* e.g. it returns something like "com/foo/OuterClass$InnerClass1$InnerClass2.class"
*/
private String classToEntryPath(Class<?> clazz) {
- String name = "";
- Class<?> parent;
- while ((parent = clazz.getEnclosingClass()) != null) {
- name = "$" + clazz.getSimpleName() + name;
- clazz = parent;
- }
- return classNameToEntryPath(clazz.getCanonicalName() + name);
+ return classNameToEntryPath(clazz.getName());
}
/**
@@ -337,7 +354,7 @@ public class AsmGenerator {
ClassVisitor cv = cw;
if (mReplaceMethodCallsClasses.contains(className)) {
- cv = new ReplaceMethodCallsAdapter(cv);
+ cv = new ReplaceMethodCallsAdapter(cv, className);
}
cv = new RefactorClassAdapter(cv, mRefactorClasses);
@@ -345,6 +362,10 @@ public class AsmGenerator {
cv = new RenameClassAdapter(cv, className, newName);
}
+ String binaryNewName = newName.replace('/', '.');
+ if (mInjectedMethodsMap.keySet().contains(binaryNewName)) {
+ cv = new InjectMethodsAdapter(cv, mInjectedMethodsMap.get(binaryNewName));
+ }
cv = new TransformClassAdapter(mLog, mStubMethods, mDeleteReturns.get(className),
newName, cv, stubNativesOnly);
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 8f50c5d..499bea4 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
@@ -26,7 +26,9 @@ import com.android.tools.layoutlib.java.System_Delegate;
import com.android.tools.layoutlib.java.UnsafeByteSequence;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
/**
@@ -105,6 +107,7 @@ public final class CreateInfo implements ICreateInfo {
return JAVA_PKG_CLASSES;
}
+ @Override
public Set<String> getExcludedClasses() {
String[] refactoredClasses = getJavaPkgClasses();
int count = refactoredClasses.length / 2 + EXCLUDED_CLASSES.length;
@@ -115,6 +118,12 @@ public final class CreateInfo implements ICreateInfo {
excludedClasses.addAll(Arrays.asList(EXCLUDED_CLASSES));
return excludedClasses;
}
+
+ @Override
+ public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
+ return INJECTED_METHODS;
+ }
+
//-----
/**
@@ -127,6 +136,8 @@ public final class CreateInfo implements ICreateInfo {
ICreateInfo.class,
CreateInfo.class,
LayoutlibDelegate.class,
+ InjectMethodRunnable.class,
+ InjectMethodRunnables.class,
/* Java package classes */
AutoCloseable.class,
Objects.class,
@@ -156,12 +167,14 @@ public final class CreateInfo implements ICreateInfo {
"android.os.HandlerThread#run",
"android.preference.Preference#getView",
"android.text.format.DateFormat#is24HourFormat",
+ "android.text.Hyphenator#getSystemHyphenatorLocation",
"android.util.Xml#newPullParser",
"android.view.Choreographer#getRefreshRate",
"android.view.Display#updateDisplayInfoLocked",
"android.view.Display#getWindowManager",
"android.view.LayoutInflater#rInflate",
"android.view.LayoutInflater#parseInclude",
+ "android.view.View#getWindowToken",
"android.view.View#isInEditMode",
"android.view.ViewRootImpl#isInTouchMode",
"android.view.WindowManagerGlobal#getWindowManagerService",
@@ -231,7 +244,6 @@ public final class CreateInfo implements ICreateInfo {
"android.text.AndroidBidi",
"android.text.StaticLayout",
"android.view.Display",
- "libcore.icu.DateIntervalFormat",
"libcore.icu.ICU",
};
@@ -254,7 +266,6 @@ public final class CreateInfo implements ICreateInfo {
"android.view.SurfaceView", "android.view._Original_SurfaceView",
"android.view.accessibility.AccessibilityManager", "android.view.accessibility._Original_AccessibilityManager",
"android.webkit.WebView", "android.webkit._Original_WebView",
- "com.android.internal.policy.PolicyManager", "com.android.internal.policy._Original_PolicyManager",
};
/**
@@ -287,5 +298,10 @@ public final class CreateInfo implements ICreateInfo {
private final static String[] DELETE_RETURNS =
new String[] {
null }; // separator, for next class/methods list.
-}
+ private final static Map<String, InjectMethodRunnable> INJECTED_METHODS =
+ new HashMap<String, InjectMethodRunnable>(1) {{
+ put("android.content.Context",
+ InjectMethodRunnables.CONTEXT_GET_FRAMEWORK_CLASS_LOADER);
+ }};
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java
index 7690fcd..61b64a2 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java
@@ -307,7 +307,9 @@ public class DependencyFinder {
try {
// exclude classes that are part of the default JRE (the one executing this program)
- if (getClass().getClassLoader().loadClass(className) != null) {
+ // or in java package (we won't be able to load them anyway).
+ if (className.startsWith("java.") ||
+ getClass().getClassLoader().loadClass(className) != null) {
return;
}
} catch (ClassNotFoundException e) {
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 e49a668..54b1fe6 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
@@ -16,6 +16,9 @@
package com.android.tools.layoutlib.create;
+import org.objectweb.asm.ClassVisitor;
+
+import java.util.Map;
import java.util.Set;
/**
@@ -27,33 +30,33 @@ public interface ICreateInfo {
* Returns the list of class from layoutlib_create to inject in layoutlib.
* The list can be empty but must not be null.
*/
- public abstract Class<?>[] getInjectedClasses();
+ Class<?>[] getInjectedClasses();
/**
* Returns the list of methods to rewrite as delegates.
* The list can be empty but must not be null.
*/
- public abstract String[] getDelegateMethods();
+ String[] getDelegateMethods();
/**
* Returns the list of classes on which to delegate all native methods.
* The list can be empty but must not be null.
*/
- public abstract String[] getDelegateClassNatives();
+ 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.
*/
- public abstract String[] getOverriddenMethods();
+ 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.
*/
- public abstract String[] getRenamedClasses();
+ String[] getRenamedClasses();
/**
* Returns the list of classes for which the methods returning them should be deleted.
@@ -62,7 +65,7 @@ public interface ICreateInfo {
* the methods to delete.
* The list can be empty but must not be null.
*/
- public abstract String[] getDeleteReturns();
+ String[] getDeleteReturns();
/**
* Returns the list of classes to refactor, must be an even list: the
@@ -70,7 +73,24 @@ public interface ICreateInfo {
* to the old class should be updated to the new class.
* The list can be empty but must not be null.
*/
- public abstract String[] getJavaPkgClasses();
+ String[] getJavaPkgClasses();
+
+ Set<String> getExcludedClasses();
+
+ /**
+ * Returns a map from binary FQCN className to {@link InjectMethodRunnable} which will be
+ * called to inject methods into a class.
+ * Can be empty but must not be null.
+ */
+ Map<String, InjectMethodRunnable> getInjectedMethodsMap();
- public abstract Set<String> getExcludedClasses();
+ abstract class InjectMethodRunnable {
+ /**
+ * @param cv Must be {@link ClassVisitor}. However, the param type is object so that when
+ * loading the class, ClassVisitor is not loaded. This is because when injecting
+ * CreateInfo in LayoutLib (see {@link #getInjectedClasses()}, we don't want to inject
+ * asm classes also, but still keep CreateInfo loadable.
+ */
+ public abstract void generateMethods(Object cv);
+ }
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java
new file mode 100644
index 0000000..37fc096
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import com.android.tools.layoutlib.create.ICreateInfo.InjectMethodRunnable;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.ARETURN;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+
+public class InjectMethodRunnables {
+ public static final ICreateInfo.InjectMethodRunnable CONTEXT_GET_FRAMEWORK_CLASS_LOADER
+ = new InjectMethodRunnable() {
+ @Override
+ public void generateMethods(Object classVisitor) {
+ assert classVisitor instanceof ClassVisitor;
+ ClassVisitor cv = (ClassVisitor) classVisitor;
+ // generated by compiling the class:
+ // class foo { public ClassLoader getFrameworkClassLoader() { return getClass().getClassLoader(); } }
+ // and then running ASMifier on it:
+ // java -classpath asm-debug-all-5.0.2.jar:. org.objectweb.asm.util.ASMifier foo
+ MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "getFrameworkClassLoader",
+ "()Ljava/lang/ClassLoader;", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass",
+ "()Ljava/lang/Class;");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getClassLoader",
+ "()Ljava/lang/ClassLoader;");
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ // generated code ends.
+ }
+ };
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java
new file mode 100644
index 0000000..ea2b9c9
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import com.android.tools.layoutlib.create.ICreateInfo.InjectMethodRunnable;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Injects methods into some classes.
+ */
+public class InjectMethodsAdapter extends ClassVisitor {
+
+ private final ICreateInfo.InjectMethodRunnable mRunnable;
+
+ public InjectMethodsAdapter(ClassVisitor cv, InjectMethodRunnable runnable) {
+ super(Opcodes.ASM4, cv);
+ mRunnable = runnable;
+ }
+
+ @Override
+ public void visitEnd() {
+ mRunnable.generateMethods(this);
+ super.visitEnd();
+ }
+}
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 fa570c8..2951edb 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
@@ -18,8 +18,6 @@ package com.android.tools.layoutlib.create;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -105,7 +103,7 @@ public class Main {
"com.android.internal.widget.*",
"android.text.**",
"android.graphics.*",
- "android.graphics.drawable.*",
+ "android.graphics.drawable.**",
"android.content.*",
"android.content.res.*",
"android.preference.*",
@@ -118,10 +116,14 @@ public class Main {
"android.app.DatePickerDialog", // b.android.com/28318
"android.app.TimePickerDialog", // b.android.com/61515
"com.android.internal.view.menu.ActionMenu",
+ "android.icu.**", // needed by LayoutLib
+ "android.annotation.NonNull", // annotations
+ "android.annotation.Nullable", // annotations
},
excludeClasses,
new String[] {
"com/android/i18n/phonenumbers/data/*",
+ "android/icu/impl/data/**"
});
aa.analyze();
agen.generate();
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
index 384d8ca..4369148 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
@@ -62,14 +62,13 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
// Case 1: java.lang.System.arraycopy()
METHOD_REPLACERS.add(new MethodReplacer() {
@Override
- public boolean isNeeded(String owner, String name, String desc) {
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return JAVA_LANG_SYSTEM.equals(owner) && "arraycopy".equals(name) &&
ARRAYCOPY_DESCRIPTORS.contains(desc);
}
@Override
public void replace(MethodInformation mi) {
- assert isNeeded(mi.owner, mi.name, mi.desc);
mi.desc = "(Ljava/lang/Object;ILjava/lang/Object;II)V";
}
});
@@ -81,14 +80,13 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
Type.getMethodDescriptor(STRING, Type.getType(Locale.class));
@Override
- public boolean isNeeded(String owner, String name, String desc) {
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return JAVA_LOCALE_CLASS.equals(owner) && "()Ljava/lang/String;".equals(desc) &&
("toLanguageTag".equals(name) || "getScript".equals(name));
}
@Override
public void replace(MethodInformation mi) {
- assert isNeeded(mi.owner, mi.name, mi.desc);
mi.opcode = Opcodes.INVOKESTATIC;
mi.owner = ANDROID_LOCALE_CLASS;
mi.desc = LOCALE_TO_STRING;
@@ -103,7 +101,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
Type.getType(Locale.class), STRING);
@Override
- public boolean isNeeded(String owner, String name, String desc) {
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return JAVA_LOCALE_CLASS.equals(owner) &&
("adjustLanguageCode".equals(name) && desc.equals(STRING_TO_STRING) ||
"forLanguageTag".equals(name) && desc.equals(STRING_TO_LOCALE));
@@ -111,7 +109,6 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
@Override
public void replace(MethodInformation mi) {
- assert isNeeded(mi.owner, mi.name, mi.desc);
mi.owner = ANDROID_LOCALE_CLASS;
}
});
@@ -119,14 +116,13 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
// Case 4: java.lang.System.log?()
METHOD_REPLACERS.add(new MethodReplacer() {
@Override
- public boolean isNeeded(String owner, String name, String desc) {
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return JAVA_LANG_SYSTEM.equals(owner) && name.length() == 4
&& name.startsWith("log");
}
@Override
public void replace(MethodInformation mi) {
- assert isNeeded(mi.owner, mi.name, mi.desc);
assert mi.desc.equals("(Ljava/lang/String;Ljava/lang/Throwable;)V")
|| mi.desc.equals("(Ljava/lang/String;)V");
mi.name = "log";
@@ -142,7 +138,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
private final String LINKED_HASH_MAP = Type.getInternalName(LinkedHashMap.class);
@Override
- public boolean isNeeded(String owner, String name, String desc) {
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return LINKED_HASH_MAP.equals(owner) &&
"eldest".equals(name) &&
VOID_TO_MAP_ENTRY.equals(desc);
@@ -150,26 +146,64 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
@Override
public void replace(MethodInformation mi) {
- assert isNeeded(mi.owner, mi.name, mi.desc);
mi.opcode = Opcodes.INVOKESTATIC;
mi.owner = Type.getInternalName(LinkedHashMap_Delegate.class);
mi.desc = Type.getMethodDescriptor(
Type.getType(Map.Entry.class), Type.getType(LinkedHashMap.class));
}
});
+
+ // Case 6: android.content.Context.getClassLoader() in LayoutInflater
+ METHOD_REPLACERS.add(new MethodReplacer() {
+ // When LayoutInflater asks for a class loader, we must return the class loader that
+ // cannot return app's custom views/classes. This is so that in case of any failure
+ // or exception when instantiating the views, the IDE can replace it with a mock view
+ // and have proper error handling. However, if a custom view asks for the class
+ // loader, we must return a class loader that can find app's custom views as well.
+ // Thus, we rewrite the call to get class loader in LayoutInflater to
+ // getFrameworkClassLoader and inject a new method in Context. This leaves the normal
+ // method: Context.getClassLoader() free to be used by the apps.
+ private final String VOID_TO_CLASS_LOADER =
+ Type.getMethodDescriptor(Type.getType(ClassLoader.class));
+
+ @Override
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
+ return owner.equals("android/content/Context") &&
+ sourceClass.equals("android/view/LayoutInflater") &&
+ name.equals("getClassLoader") &&
+ desc.equals(VOID_TO_CLASS_LOADER);
+ }
+
+ @Override
+ public void replace(MethodInformation mi) {
+ mi.name = "getFrameworkClassLoader";
+ }
+ });
}
- public static boolean isReplacementNeeded(String owner, String name, String desc) {
+ /**
+ * If a method some.package.Class.Method(args) is called from some.other.Class,
+ * @param owner some/package/Class
+ * @param name Method
+ * @param desc (args)returnType
+ * @param sourceClass some/other/Class
+ * @return if the method invocation needs to be replaced by some other class.
+ */
+ public static boolean isReplacementNeeded(String owner, String name, String desc,
+ String sourceClass) {
for (MethodReplacer replacer : METHOD_REPLACERS) {
- if (replacer.isNeeded(owner, name, desc)) {
+ if (replacer.isNeeded(owner, name, desc, sourceClass)) {
return true;
}
}
return false;
}
- public ReplaceMethodCallsAdapter(ClassVisitor cv) {
+ private final String mOriginalClassName;
+
+ public ReplaceMethodCallsAdapter(ClassVisitor cv, String originalClassName) {
super(Opcodes.ASM4, cv);
+ mOriginalClassName = originalClassName;
}
@Override
@@ -187,7 +221,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
for (MethodReplacer replacer : METHOD_REPLACERS) {
- if (replacer.isNeeded(owner, name, desc)) {
+ if (replacer.isNeeded(owner, name, desc, mOriginalClassName)) {
MethodInformation mi = new MethodInformation(opcode, owner, name, desc);
replacer.replace(mi);
opcode = mi.opcode;
@@ -216,13 +250,12 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
}
private interface MethodReplacer {
- public boolean isNeeded(String owner, String name, String desc);
+ boolean isNeeded(String owner, String name, String desc, String sourceClass);
/**
* Updates the MethodInformation with the new values of the method attributes -
* opcode, owner, name and desc.
- *
*/
- public void replace(MethodInformation mi);
+ void replace(MethodInformation mi);
}
}
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 cf91386..2c21470 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
@@ -19,7 +19,9 @@ package com.android.tools.layoutlib.create;
import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import org.junit.After;
@@ -32,13 +34,17 @@ import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@@ -130,6 +136,11 @@ public class AsmGeneratorTest {
// methods deleted from their return type.
return new String[0];
}
+
+ @Override
+ public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
+ return new HashMap<String, InjectMethodRunnable>(0);
+ }
};
AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
@@ -200,6 +211,11 @@ public class AsmGeneratorTest {
// methods deleted from their return type.
return new String[0];
}
+
+ @Override
+ public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
+ return new HashMap<String, InjectMethodRunnable>(0);
+ }
};
AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
@@ -278,6 +294,11 @@ public class AsmGeneratorTest {
// methods deleted from their return type.
return new String[0];
}
+
+ @Override
+ public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
+ return new HashMap<String, InjectMethodRunnable>(0);
+ }
};
AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
@@ -303,6 +324,118 @@ public class AsmGeneratorTest {
filesFound.keySet().toArray());
}
+ @Test
+ public void testMethodInjection() throws IOException, LogAbortException,
+ ClassNotFoundException, IllegalAccessException, InstantiationException,
+ NoSuchMethodException, InvocationTargetException {
+ ICreateInfo ci = new ICreateInfo() {
+ @Override
+ public Class<?>[] getInjectedClasses() {
+ return new Class<?>[0];
+ }
+
+ @Override
+ public String[] getDelegateMethods() {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getDelegateClassNatives() {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getOverriddenMethods() {
+ // methods to force override
+ return new String[0];
+ }
+
+ @Override
+ public String[] getRenamedClasses() {
+ // classes to rename (so that we can replace them)
+ return new String[0];
+ }
+
+ @Override
+ public String[] getJavaPkgClasses() {
+ // classes to refactor (so that we can replace them)
+ return new String[0];
+ }
+
+ @Override
+ public Set<String> getExcludedClasses() {
+ return new HashSet<String>(0);
+ }
+
+ @Override
+ public String[] getDeleteReturns() {
+ // methods deleted from their return type.
+ return new String[0];
+ }
+
+ @Override
+ public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
+ HashMap<String, InjectMethodRunnable> map =
+ new HashMap<String, InjectMethodRunnable>(1);
+ map.put("mock_android.util.EmptyArray",
+ InjectMethodRunnables.CONTEXT_GET_FRAMEWORK_CLASS_LOADER);
+ return map;
+ }
+ };
+
+ AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
+ AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
+ null, // derived from
+ new String[] { // include classes
+ "**"
+ },
+ ci.getExcludedClasses(),
+ new String[] { /* include files */
+ "mock_android/data/data*"
+ });
+ aa.analyze();
+ agen.generate();
+ Map<String, ClassReader> output = new TreeMap<String, ClassReader>();
+ Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ parseZip(mOsDestJar, output, filesFound);
+ final String modifiedClass = "mock_android.util.EmptyArray";
+ final String modifiedClassPath = modifiedClass.replace('.', '/').concat(".class");
+ ZipFile zipFile = new ZipFile(mOsDestJar);
+ ZipEntry entry = zipFile.getEntry(modifiedClassPath);
+ assertNotNull(entry);
+ final byte[] bytes;
+ final InputStream inputStream = zipFile.getInputStream(entry);
+ try {
+ bytes = getByteArray(inputStream);
+ } finally {
+ inputStream.close();
+ }
+ ClassLoader classLoader = new ClassLoader(getClass().getClassLoader()) {
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ if (name.equals(modifiedClass)) {
+ return defineClass(null, bytes, 0, bytes.length);
+ }
+ throw new ClassNotFoundException(name + " not found.");
+ }
+ };
+ Class<?> emptyArrayClass = classLoader.loadClass(modifiedClass);
+ Object emptyArrayInstance = emptyArrayClass.newInstance();
+ Method method = emptyArrayClass.getMethod("getFrameworkClassLoader");
+ Object cl = method.invoke(emptyArrayInstance);
+ assertEquals(classLoader, cl);
+ }
+
+ private static byte[] getByteArray(InputStream stream) throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int read;
+ while ((read = stream.read(buffer, 0, buffer.length)) > -1) {
+ bos.write(buffer, 0, read);
+ }
+ return bos.toByteArray();
+ }
+
private void parseZip(String jarPath,
Map<String, ClassReader> classes,
Map<String, InputStream> filesFound) throws IOException {
diff --git a/tools/layoutlib/rename_font/build_font.py b/tools/layoutlib/rename_font/build_font.py
index c747d92..9713a4c 100755
--- a/tools/layoutlib/rename_font/build_font.py
+++ b/tools/layoutlib/rename_font/build_font.py
@@ -209,15 +209,18 @@ def ends_in_regular(string):
def get_version(string):
- # The string must begin with 'Version n.nn '
- # to extract n.nn, we return the second entry in the split strings.
string = string.strip()
- if not string.startswith('Version '):
- raise InvalidFontException('mal-formed font version')
- return sanitize(string.split()[1])
+ # The spec says that the version string should start with "Version ". But not
+ # all fonts do. So, we return the complete string if it doesn't start with
+ # the prefix, else we return the rest of the string after sanitizing it.
+ prefix = 'Version '
+ if string.startswith(prefix):
+ string = string[len(prefix):]
+ return sanitize(string)
def sanitize(string):
+ """ Remove non-standard chars. """
return re.sub(r'[^\w-]+', '', string)
if __name__ == '__main__':
diff --git a/tools/layoutlib/rename_font/build_font_single.py b/tools/layoutlib/rename_font/build_font_single.py
index 5f7dad9..4245cdc 100755
--- a/tools/layoutlib/rename_font/build_font_single.py
+++ b/tools/layoutlib/rename_font/build_font_single.py
@@ -193,15 +193,18 @@ def ends_in_regular(string):
def get_version(string):
- # The string must begin with 'Version n.nn '
- # to extract n.nn, we return the second entry in the split strings.
string = string.strip()
- if not string.startswith('Version '):
- raise InvalidFontException('mal-formed font version')
- return sanitize(string.split()[1])
+ # The spec says that the version string should start with "Version ". But not
+ # all fonts do. So, we return the complete string if it doesn't start with
+ # the prefix, else we return the rest of the string after sanitizing it.
+ prefix = 'Version '
+ if string.startswith(prefix):
+ string = string[len(prefix):]
+ return sanitize(string)
def sanitize(string):
+ """ Remove non-standard chars. """
return re.sub(r'[^\w-]+', '', string)
if __name__ == '__main__':
diff --git a/tools/obbtool/Android.mk b/tools/obbtool/Android.mk
index 78d7253..1b1f63e 100644
--- a/tools/obbtool/Android.mk
+++ b/tools/obbtool/Android.mk
@@ -13,7 +13,7 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
Main.cpp
-LOCAL_CFLAGS := -Wall -Werror
+LOCAL_CFLAGS := -Wall -Werror -Wno-mismatched-tags
#LOCAL_C_INCLUDES +=
@@ -36,7 +36,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE := pbkdf2gen
LOCAL_MODULE_TAGS := optional
-LOCAL_CFLAGS := -Wall -Werror
+LOCAL_CFLAGS := -Wall -Werror -Wno-mismatched-tags
LOCAL_SRC_FILES := pbkdf2gen.cpp
LOCAL_LDLIBS += -ldl
LOCAL_STATIC_LIBRARIES := libcrypto_static
diff --git a/tools/orientationplot/README.txt b/tools/orientationplot/README.txt
index d53f65e..958207d 100644
--- a/tools/orientationplot/README.txt
+++ b/tools/orientationplot/README.txt
@@ -9,6 +9,8 @@ PREREQUISITES
2. numpy
3. matplotlib
+eg. sudo apt-get install python-numpy python-matplotlib
+
USAGE
-----
diff --git a/tools/orientationplot/orientationplot.py b/tools/orientationplot/orientationplot.py
index 6fc3922..77ed074 100755
--- a/tools/orientationplot/orientationplot.py
+++ b/tools/orientationplot/orientationplot.py
@@ -440,7 +440,7 @@ class Plotter:
# Notice
print "Window Orientation Listener plotting tool"
print "-----------------------------------------\n"
-print "Please turn on the Window Orientation Listener logging in Development Settings."
+print "Please turn on the Window Orientation Listener logging. See README.txt."
# Start adb.
print "Starting adb logcat.\n"
diff --git a/tools/split-select/Android.mk b/tools/split-select/Android.mk
index 013e570..d9ddf08 100644
--- a/tools/split-select/Android.mk
+++ b/tools/split-select/Android.mk
@@ -52,7 +52,8 @@ hostStaticLibs := \
libutils \
libcutils \
libexpat \
- libziparchive-host
+ libziparchive-host \
+ libbase
cFlags := -Wall -Werror
diff --git a/tools/split-select/Grouper.cpp b/tools/split-select/Grouper.cpp
index 22685cd..55e52fc 100644
--- a/tools/split-select/Grouper.cpp
+++ b/tools/split-select/Grouper.cpp
@@ -34,7 +34,6 @@ groupByMutualExclusivity(const Vector<SplitDescription>& splits) {
// Find mutually exclusive splits and group them.
KeyedVector<SplitDescription, SortedVector<SplitDescription> > densityGroups;
KeyedVector<SplitDescription, SortedVector<SplitDescription> > abiGroups;
- KeyedVector<SplitDescription, SortedVector<SplitDescription> > localeGroups;
const size_t splitCount = splits.size();
for (size_t i = 0; i < splitCount; i++) {
const SplitDescription& split = splits[i];
@@ -47,10 +46,6 @@ groupByMutualExclusivity(const Vector<SplitDescription>& splits) {
SplitDescription key(split);
key.abi = abi::Variant_none;
appendValue(abiGroups, key, split);
- } else if (split.config.locale != 0) {
- SplitDescription key(split);
- key.config.clearLocale();
- appendValue(localeGroups, key, split);
} else {
groups.add();
groups.editTop().add(split);
@@ -67,10 +62,6 @@ groupByMutualExclusivity(const Vector<SplitDescription>& splits) {
groups.add(abiGroups[i]);
}
- const size_t localeCount = localeGroups.size();
- for (size_t i = 0; i < localeCount; i++) {
- groups.add(localeGroups[i]);
- }
return groups;
}
diff --git a/tools/split-select/Grouper_test.cpp b/tools/split-select/Grouper_test.cpp
index a5f9c5a..7294a86 100644
--- a/tools/split-select/Grouper_test.cpp
+++ b/tools/split-select/Grouper_test.cpp
@@ -37,6 +37,8 @@ protected:
addSplit(splits, "en-rUS-sw300dp-xhdpi");
addSplit(splits, "large");
addSplit(splits, "pl-rPL");
+ addSplit(splits, "fr-rCA");
+ addSplit(splits, "fr");
addSplit(splits, "xlarge");
addSplit(splits, "en-rUS-sw600dp-xhdpi");
addSplit(splits, "en-rUS-sw300dp-hdpi");
@@ -64,7 +66,7 @@ protected:
};
TEST_F(GrouperTest, shouldHaveCorrectNumberOfGroups) {
- EXPECT_EQ(12u, mGroups.size());
+ EXPECT_EQ(15u, mGroups.size());
}
TEST_F(GrouperTest, shouldGroupDensities) {
@@ -79,7 +81,10 @@ TEST_F(GrouperTest, shouldGroupAbi) {
}
TEST_F(GrouperTest, shouldGroupLocale) {
- expectHasGroupWithSplits("pl-rPL", "de-rDE");
+ expectHasGroupWithSplits("pl-rPL");
+ expectHasGroupWithSplits("de-rDE");
+ expectHasGroupWithSplits("fr");
+ expectHasGroupWithSplits("fr-rCA");
}
TEST_F(GrouperTest, shouldGroupEachSplitIntoItsOwnGroup) {