diff options
Diffstat (limited to 'tools')
57 files changed, 3130 insertions, 233 deletions
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp index 117fc24..b7f64f6 100644 --- a/tools/aapt/AaptAssets.cpp +++ b/tools/aapt/AaptAssets.cpp @@ -1141,9 +1141,10 @@ bail: ssize_t AaptAssets::slurpFullTree(Bundle* bundle, const String8& srcDir, const AaptGroupEntry& kind, const String8& resType, - sp<FilePathStore>& fullResPaths) + sp<FilePathStore>& fullResPaths, + const bool overwrite) { - ssize_t res = AaptDir::slurpFullTree(bundle, srcDir, kind, resType, fullResPaths); + ssize_t res = AaptDir::slurpFullTree(bundle, srcDir, kind, resType, fullResPaths, overwrite); if (res > 0) { mGroupEntries.add(kind); } diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h index d809c5b..7ae5368 100644 --- a/tools/aapt/AaptAssets.h +++ b/tools/aapt/AaptAssets.h @@ -591,7 +591,8 @@ private: const String8& srcDir, const AaptGroupEntry& kind, const String8& resType, - sp<FilePathStore>& fullResPaths); + sp<FilePathStore>& fullResPaths, + const bool overwrite=false); ssize_t slurpResourceTree(Bundle* bundle, const String8& srcDir); ssize_t slurpResourceZip(Bundle* bundle, const char* filename); diff --git a/tools/aapt/AaptConfig.cpp b/tools/aapt/AaptConfig.cpp index 32a0cd3..ede9e99 100644 --- a/tools/aapt/AaptConfig.cpp +++ b/tools/aapt/AaptConfig.cpp @@ -21,6 +21,7 @@ #include "AaptAssets.h" #include "AaptUtil.h" #include "ResourceFilter.h" +#include "SdkConstants.h" using android::String8; using android::Vector; @@ -240,7 +241,9 @@ void applyVersionForCompatibility(ConfigDescription* config) { } uint16_t minSdk = 0; - if (config->smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY + 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; @@ -255,8 +258,6 @@ void applyVersionForCompatibility(ConfigDescription* config) { != ResTable_config::SCREENLONG_ANY || config->density != ResTable_config::DENSITY_DEFAULT) { minSdk = SDK_DONUT; - } else if ((config->density == ResTable_config::DENSITY_ANY)) { - minSdk = SDK_L; } if (minSdk > config->sdkVersion) { @@ -794,4 +795,23 @@ bool isSameExcept(const ResTable_config& a, const ResTable_config& b, int axisMa return a.diff(b) == axisMask; } +bool isDensityOnly(const ResTable_config& config) { + if (config.density == ResTable_config::DENSITY_DEFAULT) { + return false; + } + + if (config.density == ResTable_config::DENSITY_ANY) { + if (config.sdkVersion != SDK_LOLLIPOP) { + // Someone modified the sdkVersion from the default, this is not safe to assume. + return false; + } + } else if (config.sdkVersion != SDK_DONUT) { + return false; + } + + const uint32_t mask = ResTable_config::CONFIG_DENSITY | ResTable_config::CONFIG_VERSION; + const ConfigDescription nullConfig; + return (nullConfig.diff(config) & ~mask) == 0; +} + } // namespace AaptConfig diff --git a/tools/aapt/AaptConfig.h b/tools/aapt/AaptConfig.h index 2963539..f73a508 100644 --- a/tools/aapt/AaptConfig.h +++ b/tools/aapt/AaptConfig.h @@ -80,6 +80,12 @@ android::String8 getVersion(const android::ResTable_config& config); */ bool isSameExcept(const android::ResTable_config& a, const android::ResTable_config& b, int configMask); +/** + * Returns true if the configuration only has the density specified. In the case + * of 'anydpi', the version is ignored. + */ +bool isDensityOnly(const android::ResTable_config& config); + } // namespace AaptConfig #endif // __AAPT_CONFIG_H diff --git a/tools/aapt/AaptUtil.h b/tools/aapt/AaptUtil.h index 47a704a..89e1ee8 100644 --- a/tools/aapt/AaptUtil.h +++ b/tools/aapt/AaptUtil.h @@ -14,9 +14,11 @@ * limitations under the License. */ -#ifndef __AAPT_UTIL_H -#define __AAPT_UTIL_H +#ifndef H_AAPT_UTIL +#define H_AAPT_UTIL +#include <utils/KeyedVector.h> +#include <utils/SortedVector.h> #include <utils/String8.h> #include <utils/Vector.h> @@ -25,6 +27,38 @@ namespace AaptUtil { android::Vector<android::String8> split(const android::String8& str, const char sep); android::Vector<android::String8> splitAndLowerCase(const android::String8& str, const char sep); +template <typename KEY, typename VALUE> +void appendValue(android::KeyedVector<KEY, android::Vector<VALUE> >& keyedVector, + const KEY& key, const VALUE& value); + +template <typename KEY, typename VALUE> +void appendValue(android::KeyedVector<KEY, android::SortedVector<VALUE> >& keyedVector, + const KEY& key, const VALUE& value); + +// +// Implementations +// + +template <typename KEY, typename VALUE> +void appendValue(android::KeyedVector<KEY, android::Vector<VALUE> >& keyedVector, + const KEY& key, const VALUE& value) { + ssize_t idx = keyedVector.indexOfKey(key); + if (idx < 0) { + idx = keyedVector.add(key, android::Vector<VALUE>()); + } + keyedVector.editValueAt(idx).add(value); +} + +template <typename KEY, typename VALUE> +void appendValue(android::KeyedVector<KEY, android::SortedVector<VALUE> >& keyedVector, + const KEY& key, const VALUE& value) { + ssize_t idx = keyedVector.indexOfKey(key); + if (idx < 0) { + idx = keyedVector.add(key, android::SortedVector<VALUE>()); + } + keyedVector.editValueAt(idx).add(value); +} + } // namespace AaptUtil -#endif // __AAPT_UTIL_H +#endif // H_AAPT_UTIL diff --git a/tools/aapt/AaptXml.cpp b/tools/aapt/AaptXml.cpp index 708e405..b04a55d 100644 --- a/tools/aapt/AaptXml.cpp +++ b/tools/aapt/AaptXml.cpp @@ -41,7 +41,7 @@ static String8 getStringAttributeAtIndex(const ResXMLTree& tree, ssize_t attrInd } size_t len; - const uint16_t* str = tree.getAttributeStringValue(attrIndex, &len); + const char16_t* str = tree.getAttributeStringValue(attrIndex, &len); return str ? String8(str, len) : String8(); } @@ -103,7 +103,7 @@ String8 getResolvedAttribute(const ResTable& resTable, const ResXMLTree& tree, if (tree.getAttributeValue(idx, &value) != NO_ERROR) { if (value.dataType == Res_value::TYPE_STRING) { size_t len; - const uint16_t* str = tree.getAttributeStringValue(idx, &len); + const char16_t* str = tree.getAttributeStringValue(idx, &len); return str ? String8(str, len) : String8(); } resTable.resolveReference(&value, 0); diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk index 2cbabe1..bc9c1f7 100644 --- a/tools/aapt/Android.mk +++ b/tools/aapt/Android.mk @@ -33,20 +33,20 @@ aaptSources := \ Command.cpp \ CrunchCache.cpp \ FileFinder.cpp \ + Images.cpp \ Package.cpp \ - StringPool.cpp \ - XMLNode.cpp \ + pseudolocalize.cpp \ + qsort_r_compat.c \ + Resource.cpp \ ResourceFilter.cpp \ ResourceIdCache.cpp \ ResourceTable.cpp \ - Images.cpp \ - Resource.cpp \ - pseudolocalize.cpp \ SourcePos.cpp \ + StringPool.cpp \ WorkQueue.cpp \ + XMLNode.cpp \ ZipEntry.cpp \ - ZipFile.cpp \ - qsort_r_compat.c + ZipFile.cpp aaptTests := \ tests/AaptConfig_test.cpp \ @@ -88,16 +88,13 @@ endif include $(CLEAR_VARS) LOCAL_MODULE := libaapt - -LOCAL_SRC_FILES := $(aaptSources) -LOCAL_C_INCLUDES += $(aaptCIncludes) - -LOCAL_CFLAGS += -Wno-format-y2k -LOCAL_CFLAGS += -DSTATIC_ANDROIDFW_FOR_TOOLS -LOCAL_CFLAGS += $(aaptCFlags) +LOCAL_CFLAGS += -Wno-format-y2k -DSTATIC_ANDROIDFW_FOR_TOOLS $(aaptCFlags) +LOCAL_CPPFLAGS += $(aaptCppFlags) ifeq (darwin,$(HOST_OS)) LOCAL_CFLAGS += -D_DARWIN_UNLIMITED_STREAMS endif +LOCAL_C_INCLUDES += $(aaptCIncludes) +LOCAL_SRC_FILES := $(aaptSources) include $(BUILD_HOST_STATIC_LIBRARY) @@ -108,15 +105,11 @@ include $(BUILD_HOST_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := aapt - -LOCAL_SRC_FILES := $(aaptMain) - -LOCAL_STATIC_LIBRARIES += \ - libaapt \ - $(aaptHostStaticLibs) - -LOCAL_LDLIBS += $(aaptHostLdLibs) LOCAL_CFLAGS += $(aaptCFlags) +LOCAL_CPPFLAGS += $(aaptCppFlags) +LOCAL_LDLIBS += $(aaptHostLdLibs) +LOCAL_SRC_FILES := $(aaptMain) +LOCAL_STATIC_LIBRARIES += libaapt $(aaptHostStaticLibs) include $(BUILD_HOST_EXECUTABLE) @@ -127,16 +120,12 @@ include $(BUILD_HOST_EXECUTABLE) include $(CLEAR_VARS) LOCAL_MODULE := libaapt_tests - +LOCAL_CFLAGS += $(aaptCFlags) +LOCAL_CPPFLAGS += $(aaptCppFlags) +LOCAL_LDLIBS += $(aaptHostLdLibs) LOCAL_SRC_FILES += $(aaptTests) LOCAL_C_INCLUDES += $(LOCAL_PATH) - -LOCAL_STATIC_LIBRARIES += \ - libaapt \ - $(aaptHostStaticLibs) - -LOCAL_LDLIBS += $(aaptHostLdLibs) -LOCAL_CFLAGS += $(aaptCFlags) +LOCAL_STATIC_LIBRARIES += libaapt $(aaptHostStaticLibs) include $(BUILD_HOST_NATIVE_TEST) @@ -148,13 +137,12 @@ ifneq ($(SDK_ONLY),true) include $(CLEAR_VARS) LOCAL_MODULE := aapt - +LOCAL_CFLAGS += $(aaptCFlags) LOCAL_SRC_FILES := $(aaptSources) $(aaptMain) LOCAL_C_INCLUDES += \ $(aaptCIncludes) \ bionic \ external/stlport/stlport - LOCAL_SHARED_LIBRARIES := \ libandroidfw \ libutils \ @@ -162,14 +150,10 @@ LOCAL_SHARED_LIBRARIES := \ libpng \ liblog \ libz - LOCAL_STATIC_LIBRARIES := \ libstlport_static \ libexpat_static -LOCAL_CFLAGS += $(aaptCFlags) -LOCAL_CPPFLAGS += -Wno-non-virtual-dtor - include $(BUILD_EXECUTABLE) endif # Not SDK_ONLY diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h index cb34448..e7cde74 100644 --- a/tools/aapt/Bundle.h +++ b/tools/aapt/Bundle.h @@ -14,18 +14,7 @@ #include <utils/String8.h> #include <utils/Vector.h> -enum { - SDK_CUPCAKE = 3, - SDK_DONUT = 4, - SDK_ECLAIR = 5, - SDK_ECLAIR_0_1 = 6, - SDK_MR1 = 7, - SDK_FROYO = 8, - SDK_HONEYCOMB_MR2 = 13, - SDK_ICE_CREAM_SANDWICH = 14, - SDK_ICE_CREAM_SANDWICH_MR1 = 15, - SDK_L = 21, -}; +#include "SdkConstants.h" /* * Things we can do. @@ -190,6 +179,8 @@ public: void setVersionName(const char* val) { mVersionName = val; } bool getReplaceVersion() { return mReplaceVersion; } void setReplaceVersion(bool val) { mReplaceVersion = val; } + const android::String8& getRevisionCode() { return mRevisionCode; } + void setRevisionCode(const char* val) { mRevisionCode = android::String8(val); } const char* getCustomPackage() const { return mCustomPackage; } void setCustomPackage(const char* val) { mCustomPackage = val; } const char* getExtraPackages() const { return mExtraPackages; } @@ -308,6 +299,7 @@ private: android::String8 mFeatureOfPackage; android::String8 mFeatureAfterPackage; + android::String8 mRevisionCode; const char* mManifestMinSdkVersion; const char* mMinSdkVersion; const char* mTargetSdkVersion; diff --git a/tools/aapt/CacheUpdater.h b/tools/aapt/CacheUpdater.h index efb2453..fade53a 100644 --- a/tools/aapt/CacheUpdater.h +++ b/tools/aapt/CacheUpdater.h @@ -30,6 +30,8 @@ using namespace android; */ class CacheUpdater { public: + virtual ~CacheUpdater() {} + // Make sure all the directories along this path exist virtual void ensureDirectoriesExist(String8 path) = 0; @@ -107,4 +109,4 @@ private: Bundle* bundle; }; -#endif // CACHE_UPDATER_H
\ No newline at end of file +#endif // CACHE_UPDATER_H diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index 1e9e3e2..3fa131e 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -307,6 +307,7 @@ enum { PUBLIC_KEY_ATTR = 0x010103a6, CATEGORY_ATTR = 0x010103e8, BANNER_ATTR = 0x10103f2, + ISGAME_ATTR = 0x10103f4, }; String8 getComponentName(String8 &pkgName, String8 &componentName) { @@ -515,12 +516,10 @@ static void printFeatureGroup(const FeatureGroup& grp, const size_t numFeatures = grp.features.size(); for (size_t i = 0; i < numFeatures; i++) { - if (!grp.features[i]) { - continue; - } + const bool required = grp.features[i]; const String8& featureName = grp.features.keyAt(i); - printf(" uses-feature: name='%s'\n", + printf(" uses-feature%s: name='%s'\n", (required ? "" : "-not-required"), ResTable::normalizeForOutput(featureName.string()).string()); } @@ -1127,13 +1126,35 @@ int doDump(Bundle* bundle) error.string()); goto bail; } + + String8 banner = AaptXml::getResolvedAttribute(res, tree, BANNER_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:banner' attribute: %s\n", + error.string()); + goto bail; + } printf("application: label='%s' ", ResTable::normalizeForOutput(label.string()).string()); - printf("icon='%s'\n", ResTable::normalizeForOutput(icon.string()).string()); + printf("icon='%s'", ResTable::normalizeForOutput(icon.string()).string()); + if (banner != "") { + printf(" banner='%s'", ResTable::normalizeForOutput(banner.string()).string()); + } + printf("\n"); if (testOnly != 0) { printf("testOnly='%d'\n", testOnly); } + int32_t isGame = AaptXml::getResolvedIntegerAttribute(res, tree, + ISGAME_ATTR, 0, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:isGame' attribute: %s\n", + error.string()); + goto bail; + } + if (isGame != 0) { + printf("application-isGame\n"); + } + int32_t debuggable = AaptXml::getResolvedIntegerAttribute(res, tree, DEBUGGABLE_ATTR, 0, &error); if (error != "") { @@ -1821,7 +1842,7 @@ int doDump(Bundle* bundle) } } - if (!grp.features.isEmpty()) { + if (!grp.features.isEmpty()) { printFeatureGroup(grp); } } @@ -2512,22 +2533,17 @@ int doSingleCrunch(Bundle* bundle) int runInDaemonMode(Bundle* bundle) { std::cout << "Ready" << std::endl; - for (std::string line; std::getline(std::cin, line);) { - if (line == "quit") { + for (std::string cmd; std::getline(std::cin, cmd);) { + if (cmd == "quit") { return NO_ERROR; - } - std::stringstream ss; - ss << line; - std::string s; - - std::string command, parameterOne, parameterTwo; - std::getline(ss, command, ' '); - std::getline(ss, parameterOne, ' '); - std::getline(ss, parameterTwo, ' '); - if (command[0] == 's') { - bundle->setSingleCrunchInputFile(parameterOne.c_str()); - bundle->setSingleCrunchOutputFile(parameterTwo.c_str()); - std::cout << "Crunching " << parameterOne << std::endl; + } else if (cmd == "s") { + // Two argument crunch + std::string inputFile, outputFile; + std::getline(std::cin, inputFile); + std::getline(std::cin, outputFile); + bundle->setSingleCrunchInputFile(inputFile.c_str()); + bundle->setSingleCrunchOutputFile(outputFile.c_str()); + std::cout << "Crunching " << inputFile << std::endl; if (doSingleCrunch(bundle) != NO_ERROR) { std::cout << "Error" << std::endl; } diff --git a/tools/aapt/ConfigDescription.h b/tools/aapt/ConfigDescription.h index 779c423..4f999a2 100644 --- a/tools/aapt/ConfigDescription.h +++ b/tools/aapt/ConfigDescription.h @@ -28,10 +28,12 @@ struct ConfigDescription : public android::ResTable_config { memset(this, 0, sizeof(*this)); size = sizeof(android::ResTable_config); } + ConfigDescription(const android::ResTable_config&o) { *static_cast<android::ResTable_config*>(this) = o; size = sizeof(android::ResTable_config); } + ConfigDescription(const ConfigDescription&o) { *static_cast<android::ResTable_config*>(this) = o; } @@ -41,6 +43,7 @@ struct ConfigDescription : public android::ResTable_config { size = sizeof(android::ResTable_config); return *this; } + ConfigDescription& operator=(const ConfigDescription& o) { *static_cast<android::ResTable_config*>(this) = o; return *this; diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp index 2857b59..18b8e1e 100644 --- a/tools/aapt/Main.cpp +++ b/tools/aapt/Main.cpp @@ -11,9 +11,9 @@ #include <utils/List.h> #include <utils/Errors.h> -#include <stdlib.h> +#include <cstdlib> #include <getopt.h> -#include <assert.h> +#include <cassert> using namespace android; diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index a4c9dab..e2e83e4 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -4,6 +4,7 @@ // Build resource files from raw assets. // #include "AaptAssets.h" +#include "AaptUtil.h" #include "AaptXml.h" #include "CacheUpdater.h" #include "CrunchCache.h" @@ -13,9 +14,12 @@ #include "Main.h" #include "ResourceTable.h" #include "StringPool.h" +#include "Symbol.h" #include "WorkQueue.h" #include "XMLNode.h" +#include <algorithm> + #if HAVE_PRINTF_ZD # define ZD "%zd" # define ZD_TYPE ssize_t @@ -253,6 +257,11 @@ static status_t parsePackage(Bundle* bundle, const sp<AaptAssets>& assets, assets->setPackage(String8(block.getAttributeStringValue(nameIndex, &len))); + ssize_t revisionCodeIndex = block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "revisionCode"); + if (revisionCodeIndex >= 0) { + bundle->setRevisionCode(String8(block.getAttributeStringValue(revisionCodeIndex, &len)).string()); + } + String16 uses_sdk16("uses-sdk"); while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { @@ -261,7 +270,7 @@ static status_t parsePackage(Bundle* bundle, const sp<AaptAssets>& assets, ssize_t minSdkIndex = block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "minSdkVersion"); if (minSdkIndex >= 0) { - const uint16_t* minSdk16 = block.getAttributeStringValue(minSdkIndex, &len); + const char16_t* minSdk16 = block.getAttributeStringValue(minSdkIndex, &len); const char* minSdk8 = strdup(String8(minSdk16).string()); bundle->setManifestMinSdkVersion(minSdk8); } @@ -450,7 +459,7 @@ static int validateAttr(const String8& path, const ResTable& table, size_t len; ssize_t index = parser.indexOfAttribute(ns, attr); - const uint16_t* str; + const char16_t* str; Res_value value; if (index >= 0 && parser.getAttributeValue(index, &value) >= 0) { const ResStringPool* pool = &parser.getStrings(); @@ -503,7 +512,7 @@ static int validateAttr(const String8& path, const ResTable& table, } if (validChars) { for (size_t i=0; i<len; i++) { - uint16_t c = str[i]; + char16_t c = str[i]; const char* p = validChars; bool okay = false; while (*p) { @@ -1071,6 +1080,14 @@ status_t generateAndroidManifestForSplit(Bundle* bundle, const sp<AaptAssets>& a return UNKNOWN_ERROR; } + // Add the 'revisionCode' attribute, which is set to the original revisionCode. + if (bundle->getRevisionCode().size() > 0) { + if (!addTagAttribute(manifest, RESOURCES_ANDROID_NAMESPACE, "revisionCode", + bundle->getRevisionCode().string(), true, true)) { + return UNKNOWN_ERROR; + } + } + // Add the 'split' attribute which describes the configurations included. String8 splitName("config."); splitName.append(split->getPackageSafeName()); @@ -1550,6 +1567,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil // Re-flatten because we may have added new resource IDs // -------------------------------------------------------------- + ResTable finalResTable; sp<AaptFile> resFile; @@ -1560,6 +1578,13 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil return err; } + KeyedVector<Symbol, Vector<SymbolDefinition> > densityVaryingResources; + if (builder->getSplits().size() > 1) { + // Only look for density varying resources if we're generating + // splits. + table.getDensityVaryingResources(densityVaryingResources); + } + Vector<sp<ApkSplit> >& splits = builder->getSplits(); const size_t numSplits = splits.size(); for (size_t i = 0; i < numSplits; i++) { @@ -1583,6 +1608,63 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil return err; } } else { + ResTable resTable; + err = resTable.add(flattenedTable->getData(), flattenedTable->getSize()); + if (err != NO_ERROR) { + fprintf(stderr, "Generated resource table for split '%s' is corrupt.\n", + split->getPrintableName().string()); + return err; + } + + bool hasError = false; + const std::set<ConfigDescription>& splitConfigs = split->getConfigs(); + for (std::set<ConfigDescription>::const_iterator iter = splitConfigs.begin(); + iter != splitConfigs.end(); + ++iter) { + const ConfigDescription& config = *iter; + if (AaptConfig::isDensityOnly(config)) { + // Each density only split must contain all + // density only resources. + Res_value val; + resTable.setParameters(&config); + const size_t densityVaryingResourceCount = densityVaryingResources.size(); + for (size_t k = 0; k < densityVaryingResourceCount; k++) { + const Symbol& symbol = densityVaryingResources.keyAt(k); + ssize_t block = resTable.getResource(symbol.id, &val, true); + if (block < 0) { + // Maybe it's in the base? + finalResTable.setParameters(&config); + block = finalResTable.getResource(symbol.id, &val, true); + } + + if (block < 0) { + hasError = true; + SourcePos().error("%s has no definition for density split '%s'", + symbol.toString().string(), config.toString().string()); + + if (bundle->getVerbose()) { + const Vector<SymbolDefinition>& defs = densityVaryingResources[k]; + const size_t defCount = std::min(size_t(5), defs.size()); + for (size_t d = 0; d < defCount; d++) { + const SymbolDefinition& def = defs[d]; + def.source.error("%s has definition for %s", + symbol.toString().string(), def.config.toString().string()); + } + + if (defCount < defs.size()) { + SourcePos().error("and %d more ...", (int) (defs.size() - defCount)); + } + } + } + } + } + } + + if (hasError) { + return UNKNOWN_ERROR; + } + + // Generate the AndroidManifest for this split. sp<AaptFile> generatedManifest = new AaptFile(String8("AndroidManifest.xml"), AaptGroupEntry(), String8()); err = generateAndroidManifestForSplit(bundle, assets, split, @@ -1710,7 +1792,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil } size_t len; ssize_t index = block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "name"); - const uint16_t* id = block.getAttributeStringValue(index, &len); + const char16_t* id = block.getAttributeStringValue(index, &len); if (id == NULL) { fprintf(stderr, "%s:%d: missing name attribute in element <%s>.\n", manifestPath.string(), block.getLineNumber(), @@ -1753,7 +1835,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil hasErrors = true; } syms->addStringSymbol(String8(e), idStr, srcPos); - const uint16_t* cmt = block.getComment(&len); + const char16_t* cmt = block.getComment(&len); if (cmt != NULL && *cmt != 0) { //printf("Comment of %s: %s\n", String8(e).string(), // String8(cmt).string()); @@ -2916,17 +2998,26 @@ status_t writeProguardForLayouts(ProguardKeepSet* keep, const sp<AaptAssets>& assets) { status_t err; + const char* kClass = "class"; + const char* kFragment = "fragment"; + const String8 kTransition("transition"); + const String8 kTransitionPrefix("transition-"); // tag:attribute pairs that should be checked in layout files. KeyedVector<String8, Vector<NamespaceAttributePair> > kLayoutTagAttrPairs; - addTagAttrPair(&kLayoutTagAttrPairs, "view", NULL, "class"); - addTagAttrPair(&kLayoutTagAttrPairs, "fragment", NULL, "class"); - addTagAttrPair(&kLayoutTagAttrPairs, "fragment", RESOURCES_ANDROID_NAMESPACE, "name"); + addTagAttrPair(&kLayoutTagAttrPairs, "view", NULL, kClass); + addTagAttrPair(&kLayoutTagAttrPairs, kFragment, NULL, kClass); + addTagAttrPair(&kLayoutTagAttrPairs, kFragment, RESOURCES_ANDROID_NAMESPACE, "name"); // tag:attribute pairs that should be checked in xml files. KeyedVector<String8, Vector<NamespaceAttributePair> > kXmlTagAttrPairs; - addTagAttrPair(&kXmlTagAttrPairs, "PreferenceScreen", RESOURCES_ANDROID_NAMESPACE, "fragment"); - addTagAttrPair(&kXmlTagAttrPairs, "header", RESOURCES_ANDROID_NAMESPACE, "fragment"); + addTagAttrPair(&kXmlTagAttrPairs, "PreferenceScreen", RESOURCES_ANDROID_NAMESPACE, kFragment); + addTagAttrPair(&kXmlTagAttrPairs, "header", RESOURCES_ANDROID_NAMESPACE, kFragment); + + // tag:attribute pairs that should be checked in transition files. + KeyedVector<String8, Vector<NamespaceAttributePair> > kTransitionTagAttrPairs; + addTagAttrPair(&kTransitionTagAttrPairs, kTransition.string(), NULL, kClass); + addTagAttrPair(&kTransitionTagAttrPairs, "pathMotion", NULL, kClass); const Vector<sp<AaptDir> >& dirs = assets->resDirs(); const size_t K = dirs.size(); @@ -2945,6 +3036,9 @@ writeProguardForLayouts(ProguardKeepSet* keep, const sp<AaptAssets>& assets) } else if ((dirName == String8("menu")) || (strncmp(dirName.string(), "menu-", 5) == 0)) { startTags.add(String8("menu")); tagAttrPairs = NULL; + } else if (dirName == kTransition || (strncmp(dirName.string(), kTransitionPrefix.string(), + kTransitionPrefix.size()) == 0)) { + tagAttrPairs = &kTransitionTagAttrPairs; } else { continue; } diff --git a/tools/aapt/ResourceFilter.cpp b/tools/aapt/ResourceFilter.cpp index fc95e14..8693999 100644 --- a/tools/aapt/ResourceFilter.cpp +++ b/tools/aapt/ResourceFilter.cpp @@ -41,6 +41,13 @@ WeakResourceFilter::parse(const String8& str) // Ignore the version entry.second &= ~ResTable_config::CONFIG_VERSION; + // Ignore any densities. Those are best handled in --preferred-density + if ((entry.second & ResTable_config::CONFIG_DENSITY) != 0) { + fprintf(stderr, "warning: ignoring flag -c %s. Use --preferred-density instead.\n", entry.first.toString().string()); + entry.first.density = 0; + entry.second &= ~ResTable_config::CONFIG_DENSITY; + } + mConfigMask |= entry.second; } diff --git a/tools/aapt/ResourceIdCache.cpp b/tools/aapt/ResourceIdCache.cpp index d60a07f..8835fb0 100644 --- a/tools/aapt/ResourceIdCache.cpp +++ b/tools/aapt/ResourceIdCache.cpp @@ -9,8 +9,6 @@ #include <utils/Log.h> #include "ResourceIdCache.h" #include <map> -using namespace std; - static size_t mHits = 0; static size_t mMisses = 0; @@ -29,7 +27,7 @@ struct CacheEntry { CacheEntry(const android::String16& name, uint32_t resId) : hashedName(name), id(resId) { } }; -static map< uint32_t, CacheEntry > mIdMap; +static std::map< uint32_t, CacheEntry > mIdMap; // djb2; reasonable choice for strings when collisions aren't particularly important @@ -63,7 +61,7 @@ uint32_t ResourceIdCache::lookup(const android::String16& package, bool onlyPublic) { const String16 hashedName = makeHashableName(package, type, name, onlyPublic); const uint32_t hashcode = hash(hashedName); - map<uint32_t, CacheEntry>::iterator item = mIdMap.find(hashcode); + std::map<uint32_t, CacheEntry>::iterator item = mIdMap.find(hashcode); if (item == mIdMap.end()) { // cache miss mMisses++; diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index 4587a4b..e000a1d 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -6,10 +6,13 @@ #include "ResourceTable.h" +#include "AaptUtil.h" #include "XMLNode.h" #include "ResourceFilter.h" #include "ResourceIdCache.h" +#include "SdkConstants.h" +#include <algorithm> #include <androidfw/ResourceTypes.h> #include <utils/ByteOrder.h> #include <utils/TypeHelpers.h> @@ -17,6 +20,8 @@ #define NOISY(x) //x +static const char* kAttrPrivateType = "^attr-private"; + status_t compileXmlFile(const Bundle* bundle, const sp<AaptAssets>& assets, const String16& resourceName, @@ -399,7 +404,7 @@ static status_t compileAttribute(const sp<AaptFile>& in, ssize_t l10nIdx = block.indexOfAttribute(NULL, "localization"); if (l10nIdx >= 0) { - const uint16_t* str = block.getAttributeStringValue(l10nIdx, &len); + const char16_t* str = block.getAttributeStringValue(l10nIdx, &len); bool error; uint32_t l10n_required = parse_flags(str, len, l10nRequiredFlags, &error); if (error) { @@ -1325,7 +1330,7 @@ status_t compileResourceFile(Bundle* bundle, size_t n = block.getAttributeCount(); for (size_t i = 0; i < n; i++) { size_t length; - const uint16_t* attr = block.getAttributeName(i, &length); + const char16_t* attr = block.getAttributeName(i, &length); if (strcmp16(attr, name16.string()) == 0) { name.setTo(block.getAttributeStringValue(i, &length)); } else if (strcmp16(attr, translatable16.string()) == 0) { @@ -1441,14 +1446,14 @@ status_t compileResourceFile(Bundle* bundle, // translatable. for (size_t i = 0; i < n; i++) { size_t length; - const uint16_t* attr = block.getAttributeName(i, &length); + const char16_t* attr = block.getAttributeName(i, &length); if (strcmp16(attr, formatted16.string()) == 0) { - const uint16_t* value = block.getAttributeStringValue(i, &length); + const char16_t* value = block.getAttributeStringValue(i, &length); if (strcmp16(value, false16.string()) == 0) { curIsFormatted = false; } } else if (strcmp16(attr, translatable16.string()) == 0) { - const uint16_t* value = block.getAttributeStringValue(i, &length); + const char16_t* value = block.getAttributeStringValue(i, &length); if (strcmp16(value, false16.string()) == 0) { isTranslatable = false; } @@ -2129,8 +2134,16 @@ uint32_t ResourceTable::getResId(const String16& package, if (p == NULL) return 0; sp<Type> t = p->getTypes().valueFor(type); if (t == NULL) return 0; - sp<ConfigList> c = t->getConfigs().valueFor(name); - if (c == NULL) return 0; + sp<ConfigList> c = t->getConfigs().valueFor(name); + if (c == NULL) { + if (type != String16("attr")) { + return 0; + } + t = p->getTypes().valueFor(String16(kAttrPrivateType)); + if (t == NULL) return 0; + c = t->getConfigs().valueFor(name); + if (c == NULL) return 0; + } int32_t ei = c->getEntryIndex(); if (ei < 0) return 0; @@ -2264,7 +2277,15 @@ uint32_t ResourceTable::getCustomResource( sp<Type> t = p->getTypes().valueFor(type); if (t == NULL) return 0; sp<ConfigList> c = t->getConfigs().valueFor(name); - if (c == NULL) return 0; + if (c == NULL) { + if (type != String16("attr")) { + return 0; + } + t = p->getTypes().valueFor(String16(kAttrPrivateType)); + if (t == NULL) return 0; + c = t->getConfigs().valueFor(name); + if (c == NULL) return 0; + } int32_t ei = c->getEntryIndex(); if (ei < 0) return 0; return getResId(p, t, ei); @@ -2468,6 +2489,10 @@ status_t ResourceTable::assignResourceIds() continue; } + if (mPackageType == System) { + p->movePrivateAttrs(); + } + // This has no sense for packages being built as AppFeature (aka with a non-zero offset). status_t err = p->applyPublicTypeOrder(); if (err != NO_ERROR && firstError == NO_ERROR) { @@ -2538,15 +2563,20 @@ status_t ResourceTable::assignResourceIds() } } + // Assign resource IDs to keys in bags... for (size_t ti = 0; ti < typeCount; ti++) { sp<Type> t = p->getOrderedTypes().itemAt(ti); if (t == NULL) { continue; } + const size_t N = t->getOrderedConfigs().size(); for (size_t ci=0; ci<N; ci++) { sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci); + if (c == NULL) { + continue; + } //printf("Ordered config #%d: %p\n", ci, c.get()); const size_t N = c->getEntries().size(); for (size_t ei=0; ei<N; ei++) { @@ -2584,9 +2614,15 @@ status_t ResourceTable::addSymbols(const sp<AaptSymbols>& outSymbols) { if (t == NULL) { continue; } + const size_t N = t->getOrderedConfigs().size(); - sp<AaptSymbols> typeSymbols = - outSymbols->addNestedSymbol(String8(t->getName()), t->getPos()); + sp<AaptSymbols> typeSymbols; + if (t->getName() == String16(kAttrPrivateType)) { + typeSymbols = outSymbols->addNestedSymbol(String8("attr"), t->getPos()); + } else { + typeSymbols = outSymbols->addNestedSymbol(String8(t->getName()), t->getPos()); + } + if (typeSymbols == NULL) { return UNKNOWN_ERROR; } @@ -2952,6 +2988,10 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& for (size_t ei=0; ei<N; ei++) { sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei); + if (cl == NULL) { + continue; + } + if (cl->getPublic()) { typeSpecFlags[ei] |= htodl(ResTable_typeSpec::SPEC_PUBLIC); } @@ -2982,12 +3022,13 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& // We need to write one type chunk for each configuration for // which we have entries in this type. - const size_t NC = t->getUniqueConfigs().size(); + const SortedVector<ConfigDescription> uniqueConfigs(t->getUniqueConfigs()); + const size_t NC = uniqueConfigs.size(); const size_t typeSize = sizeof(ResTable_type) + sizeof(uint32_t)*N; for (size_t ci=0; ci<NC; ci++) { - ConfigDescription config = t->getUniqueConfigs().itemAt(ci); + const ConfigDescription& config = uniqueConfigs[ci]; NOISY(printf("Writing config %d config: imsi:%d/%d lang:%c%c cnt:%c%c " "orien:%d ui:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d " @@ -3059,7 +3100,10 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& // Build the entries inside of this type. for (size_t ei=0; ei<N; ei++) { sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei); - sp<Entry> e = cl->getEntries().valueFor(config); + sp<Entry> e = NULL; + if (cl != NULL) { + e = cl->getEntries().valueFor(config); + } // Set the offset for this entry in its type. uint32_t* index = (uint32_t*) @@ -3094,9 +3138,11 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& for (size_t i = 0; i < N; ++i) { if (!validResources[i]) { sp<ConfigList> c = t->getOrderedConfigs().itemAt(i); - fprintf(stderr, "%s: no entries written for %s/%s (0x%08x)\n", log_prefix, - String8(typeName).string(), String8(c->getName()).string(), - Res_MAKEID(p->getAssignedId() - 1, ti, i)); + if (c != NULL) { + fprintf(stderr, "%s: no entries written for %s/%s (0x%08x)\n", log_prefix, + String8(typeName).string(), String8(c->getName()).string(), + Res_MAKEID(p->getAssignedId() - 1, ti, i)); + } missing_entry = true; } } @@ -3805,11 +3851,45 @@ sp<ResourceTable::Entry> ResourceTable::Type::getEntry(const String16& entry, */ } - mUniqueConfigs.add(cdesc); - return e; } +sp<ResourceTable::ConfigList> ResourceTable::Type::removeEntry(const String16& entry) { + ssize_t idx = mConfigs.indexOfKey(entry); + if (idx < 0) { + return NULL; + } + + sp<ConfigList> removed = mConfigs.valueAt(idx); + mConfigs.removeItemsAt(idx); + + Vector<sp<ConfigList> >::iterator iter = std::find( + mOrderedConfigs.begin(), mOrderedConfigs.end(), removed); + if (iter != mOrderedConfigs.end()) { + mOrderedConfigs.erase(iter); + } + + mPublic.removeItem(entry); + return removed; +} + +SortedVector<ConfigDescription> ResourceTable::Type::getUniqueConfigs() const { + SortedVector<ConfigDescription> unique; + const size_t entryCount = mOrderedConfigs.size(); + for (size_t i = 0; i < entryCount; i++) { + if (mOrderedConfigs[i] == NULL) { + continue; + } + const DefaultKeyedVector<ConfigDescription, sp<Entry> >& configs = + mOrderedConfigs[i]->getEntries(); + const size_t configCount = configs.size(); + for (size_t j = 0; j < configCount; j++) { + unique.add(configs.keyAt(j)); + } + } + return unique; +} + status_t ResourceTable::Type::applyPublicEntryOrder() { size_t N = mOrderedConfigs.size(); @@ -3836,11 +3916,10 @@ status_t ResourceTable::Type::applyPublicEntryOrder() //printf("#%d: \"%s\"\n", i, String8(e->getName()).string()); if (e->getName() == name) { if (idx >= (int32_t)mOrderedConfigs.size()) { - p.sourcePos.error("Public entry identifier 0x%x entry index " - "is larger than available symbols (index %d, total symbols %d).\n", - p.ident, idx, mOrderedConfigs.size()); - hasError = true; - } else if (mOrderedConfigs.itemAt(idx) == NULL) { + mOrderedConfigs.resize(idx + 1); + } + + if (mOrderedConfigs.itemAt(idx) == NULL) { e->setPublic(true); e->setPublicSourcePos(p.sourcePos); mOrderedConfigs.replaceAt(e, idx); @@ -4016,6 +4095,61 @@ status_t ResourceTable::Package::applyPublicTypeOrder() return NO_ERROR; } +void ResourceTable::Package::movePrivateAttrs() { + sp<Type> attr = mTypes.valueFor(String16("attr")); + if (attr == NULL) { + // Nothing to do. + return; + } + + Vector<sp<ConfigList> > privateAttrs; + + bool hasPublic = false; + const Vector<sp<ConfigList> >& configs = attr->getOrderedConfigs(); + const size_t configCount = configs.size(); + for (size_t i = 0; i < configCount; i++) { + if (configs[i] == NULL) { + continue; + } + + if (attr->isPublic(configs[i]->getName())) { + hasPublic = true; + } else { + privateAttrs.add(configs[i]); + } + } + + // Only if we have public attributes do we create a separate type for + // private attributes. + if (!hasPublic) { + return; + } + + // Create a new type for private attributes. + sp<Type> privateAttrType = getType(String16(kAttrPrivateType), SourcePos()); + + const size_t privateAttrCount = privateAttrs.size(); + for (size_t i = 0; i < privateAttrCount; i++) { + const sp<ConfigList>& cl = privateAttrs[i]; + + // Remove the private attributes from their current type. + attr->removeEntry(cl->getName()); + + // Add it to the new type. + const DefaultKeyedVector<ConfigDescription, sp<Entry> >& entries = cl->getEntries(); + const size_t entryCount = entries.size(); + for (size_t j = 0; j < entryCount; j++) { + const sp<Entry>& oldEntry = entries[j]; + sp<Entry> entry = privateAttrType->getEntry( + cl->getName(), oldEntry->getPos(), &entries.keyAt(j)); + *entry = *oldEntry; + } + + // Move the symbols to the new type. + + } +} + sp<ResourceTable::Package> ResourceTable::getPackage(const String16& package) { if (package != mAssetsPackage) { @@ -4195,38 +4329,80 @@ bool ResourceTable::getItemValue( } /** - * Returns true if the given attribute ID comes from - * a platform version from or after L. + * Returns the SDK version at which the attribute was + * made public, or -1 if the resource ID is not an attribute + * or is not public. */ -bool ResourceTable::isAttributeFromL(uint32_t attrId) { - const uint32_t baseAttrId = 0x010103f7; - if ((attrId & 0xffff0000) != (baseAttrId & 0xffff0000)) { - return false; +int ResourceTable::getPublicAttributeSdkLevel(uint32_t attrId) const { + if (Res_GETPACKAGE(attrId) + 1 != 0x01 || Res_GETTYPE(attrId) + 1 != 0x01) { + return -1; } uint32_t specFlags; if (!mAssets->getIncludedResources().getResourceFlags(attrId, &specFlags)) { - return false; + return -1; + } + + if ((specFlags & ResTable_typeSpec::SPEC_PUBLIC) == 0) { + return -1; + } + + const size_t entryId = Res_GETENTRY(attrId); + if (entryId <= 0x021c) { + return 1; + } else if (entryId <= 0x021d) { + return 2; + } else if (entryId <= 0x0269) { + return SDK_CUPCAKE; + } else if (entryId <= 0x028d) { + return SDK_DONUT; + } else if (entryId <= 0x02ad) { + return SDK_ECLAIR; + } else if (entryId <= 0x02b3) { + return SDK_ECLAIR_0_1; + } else if (entryId <= 0x02b5) { + return SDK_ECLAIR_MR1; + } else if (entryId <= 0x02bd) { + return SDK_FROYO; + } else if (entryId <= 0x02cb) { + return SDK_GINGERBREAD; + } else if (entryId <= 0x0361) { + return SDK_HONEYCOMB; + } else if (entryId <= 0x0366) { + return SDK_HONEYCOMB_MR1; + } else if (entryId <= 0x03a6) { + return SDK_HONEYCOMB_MR2; + } else if (entryId <= 0x03ae) { + return SDK_JELLY_BEAN; + } else if (entryId <= 0x03cc) { + return SDK_JELLY_BEAN_MR1; + } else if (entryId <= 0x03da) { + return SDK_JELLY_BEAN_MR2; + } else if (entryId <= 0x03f1) { + return SDK_KITKAT; + } else if (entryId <= 0x03f6) { + return SDK_KITKAT_WATCH; + } else if (entryId <= 0x04ce) { + return SDK_LOLLIPOP; + } else { + // Anything else is marked as defined in + // SDK_LOLLIPOP_MR1 since after this + // version no attribute compat work + // needs to be done. + return SDK_LOLLIPOP_MR1; } - - return (specFlags & ResTable_typeSpec::SPEC_PUBLIC) != 0 && - (attrId & 0x0000ffff) >= (baseAttrId & 0x0000ffff); } -static bool isMinSdkVersionLOrAbove(const Bundle* bundle) { - if (bundle->getMinSdkVersion() != NULL && strlen(bundle->getMinSdkVersion()) > 0) { - const char firstChar = bundle->getMinSdkVersion()[0]; - if (firstChar >= 'L' && firstChar <= 'Z') { - // L is the code-name for the v21 release. - return true; - } - - const int minSdk = atoi(bundle->getMinSdkVersion()); - if (minSdk >= SDK_L) { - return true; - } +/** + * First check the Manifest, then check the command line flag. + */ +static int getMinSdkVersion(const Bundle* bundle) { + if (bundle->getManifestMinSdkVersion() != NULL && strlen(bundle->getManifestMinSdkVersion()) > 0) { + return atoi(bundle->getManifestMinSdkVersion()); + } else if (bundle->getMinSdkVersion() != NULL && strlen(bundle->getMinSdkVersion()) > 0) { + return atoi(bundle->getMinSdkVersion()); } - return false; + return 0; } /** @@ -4272,9 +4448,10 @@ static bool isMinSdkVersionLOrAbove(const Bundle* bundle) { * attribute will be respected. */ status_t ResourceTable::modifyForCompat(const Bundle* bundle) { - if (isMinSdkVersionLOrAbove(bundle)) { - // If this app will only ever run on L+ devices, - // we don't need to do any compatibility work. + const int minSdk = getMinSdkVersion(bundle); + if (minSdk >= SDK_LOLLIPOP_MR1) { + // Lollipop MR1 and up handles public attributes differently, no + // need to do any compat modifications. return NO_ERROR; } @@ -4313,20 +4490,19 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle) { } const ConfigDescription& config = entries.keyAt(ei); - if (config.sdkVersion >= SDK_L) { - // We don't need to do anything if the resource is - // already qualified for version 21 or higher. + if (config.sdkVersion >= SDK_LOLLIPOP_MR1) { continue; } - Vector<String16> attributesToRemove; + KeyedVector<int, Vector<String16> > attributesToRemove; const KeyedVector<String16, Item>& bag = e->getBag(); const size_t bagCount = bag.size(); for (size_t bi = 0; bi < bagCount; bi++) { const Item& item = bag.valueAt(bi); const uint32_t attrId = getResId(bag.keyAt(bi), &attr16); - if (isAttributeFromL(attrId)) { - attributesToRemove.add(bag.keyAt(bi)); + const int sdkLevel = getPublicAttributeSdkLevel(attrId); + if (sdkLevel > 1 && sdkLevel > config.sdkVersion && sdkLevel > minSdk) { + AaptUtil::appendValue(attributesToRemove, sdkLevel, bag.keyAt(bi)); } } @@ -4334,16 +4510,41 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle) { continue; } - // Duplicate the entry under the same configuration - // but with sdkVersion == SDK_L. - ConfigDescription newConfig(config); - newConfig.sdkVersion = SDK_L; - entriesToAdd.add(key_value_pair_t<ConfigDescription, sp<Entry> >( - newConfig, new Entry(*e))); + const size_t sdkCount = attributesToRemove.size(); + for (size_t i = 0; i < sdkCount; i++) { + const int sdkLevel = attributesToRemove.keyAt(i); + + // Duplicate the entry under the same configuration + // but with sdkVersion == sdkLevel. + ConfigDescription newConfig(config); + newConfig.sdkVersion = sdkLevel; + + sp<Entry> newEntry = new Entry(*e); + + // Remove all items that have a higher SDK level than + // the one we are synthesizing. + for (size_t j = 0; j < sdkCount; j++) { + if (j == i) { + continue; + } + + if (attributesToRemove.keyAt(j) > sdkLevel) { + const size_t attrCount = attributesToRemove[j].size(); + for (size_t k = 0; k < attrCount; k++) { + newEntry->removeFromBag(attributesToRemove[j][k]); + } + } + } + + entriesToAdd.add(key_value_pair_t<ConfigDescription, sp<Entry> >( + newConfig, newEntry)); + } // Remove the attribute from the original. for (size_t i = 0; i < attributesToRemove.size(); i++) { - e->removeFromBag(attributesToRemove[i]); + for (size_t j = 0; j < attributesToRemove[i].size(); j++) { + e->removeFromBag(attributesToRemove[i][j]); + } } } @@ -4360,7 +4561,7 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle) { if (bundle->getVerbose()) { entriesToAdd[i].value->getPos() .printf("using v%d attributes; synthesizing resource %s:%s/%s for configuration %s.", - SDK_L, + entriesToAdd[i].key.sdkVersion, String8(p->getName()).string(), String8(t->getName()).string(), String8(entriesToAdd[i].value->getName()).string(), @@ -4383,17 +4584,23 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle, const String16& resourceName, const sp<AaptFile>& target, const sp<XMLNode>& root) { - if (isMinSdkVersionLOrAbove(bundle)) { + const int minSdk = getMinSdkVersion(bundle); + if (minSdk >= SDK_LOLLIPOP_MR1) { + // Lollipop MR1 and up handles public attributes differently, no + // need to do any compat modifications. return NO_ERROR; } - if (target->getResourceType() == "" || target->getGroupEntry().toParams().sdkVersion >= SDK_L) { + 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. return NO_ERROR; } - Vector<key_value_pair_t<sp<XMLNode>, size_t> > attrsToRemove; + sp<XMLNode> newRoot = NULL; + ConfigDescription newConfig(target->getGroupEntry().toParams()); + newConfig.sdkVersion = SDK_LOLLIPOP_MR1; Vector<sp<XMLNode> > nodesToVisit; nodesToVisit.push(root); @@ -4402,11 +4609,31 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle, nodesToVisit.pop(); const Vector<XMLNode::attribute_entry>& attrs = node->getAttributes(); - const size_t attrCount = attrs.size(); - for (size_t i = 0; i < attrCount; i++) { + for (size_t i = 0; i < attrs.size(); i++) { const XMLNode::attribute_entry& attr = attrs[i]; - if (isAttributeFromL(attr.nameResId)) { - attrsToRemove.add(key_value_pair_t<sp<XMLNode>, size_t>(node, i)); + const int sdkLevel = getPublicAttributeSdkLevel(attr.nameResId); + if (sdkLevel > 1 && sdkLevel > config.sdkVersion && sdkLevel > minSdk) { + if (newRoot == NULL) { + newRoot = root->clone(); + } + + // Find the smallest sdk version that we need to synthesize for + // and do that one. Subsequent versions will be processed on + // the next pass. + if (sdkLevel < newConfig.sdkVersion) { + newConfig.sdkVersion = sdkLevel; + } + + if (bundle->getVerbose()) { + SourcePos(node->getFilename(), node->getStartLineNumber()).printf( + "removing attribute %s%s%s from <%s>", + String8(attr.ns).string(), + (attr.ns.size() == 0 ? "" : ":"), + String8(attr.name).string(), + String8(node->getElementName()).string()); + } + node->removeAttribute(i); + i--; } } @@ -4418,22 +4645,15 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle, } } - if (attrsToRemove.isEmpty()) { + if (newRoot == NULL) { return NO_ERROR; } - ConfigDescription newConfig(target->getGroupEntry().toParams()); - newConfig.sdkVersion = SDK_L; - // Look to see if we already have an overriding v21 configuration. sp<ConfigList> cl = getConfigList(String16(mAssets->getPackage()), String16(target->getResourceType()), resourceName); - //if (cl == NULL) { - // fprintf(stderr, "fuuuuck\n"); - //} if (cl->getEntries().indexOfKey(newConfig) < 0) { // We don't have an overriding entry for v21, so we must duplicate this one. - sp<XMLNode> newRoot = root->clone(); sp<AaptFile> newFile = new AaptFile(target->getSourceFile(), AaptGroupEntry(newConfig), target->getResourceType()); String8 resPath = String8::format("res/%s/%s", @@ -4445,7 +4665,7 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle, if (bundle->getVerbose()) { SourcePos(target->getSourceFile(), -1).printf( "using v%d attributes; synthesizing resource %s:%s/%s for configuration %s.", - SDK_L, + newConfig.sdkVersion, mAssets->getPackage().string(), newFile->getResourceType().string(), String8(resourceName).string(), @@ -4468,21 +4688,36 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle, mWorkQueue.push(item); } - const size_t removeCount = attrsToRemove.size(); - for (size_t i = 0; i < removeCount; i++) { - sp<XMLNode> node = attrsToRemove[i].key; - size_t attrIndex = attrsToRemove[i].value; - const XMLNode::attribute_entry& ae = node->getAttributes()[attrIndex]; - if (bundle->getVerbose()) { - SourcePos(node->getFilename(), node->getStartLineNumber()).printf( - "removing attribute %s%s%s from <%s>", - String8(ae.ns).string(), - (ae.ns.size() == 0 ? "" : ":"), - String8(ae.name).string(), - String8(node->getElementName()).string()); + return NO_ERROR; +} + +void ResourceTable::getDensityVaryingResources(KeyedVector<Symbol, Vector<SymbolDefinition> >& resources) { + const ConfigDescription nullConfig; + + const size_t packageCount = mOrderedPackages.size(); + for (size_t p = 0; p < packageCount; p++) { + const Vector<sp<Type> >& types = mOrderedPackages[p]->getOrderedTypes(); + const size_t typeCount = types.size(); + for (size_t t = 0; t < typeCount; t++) { + const Vector<sp<ConfigList> >& configs = types[t]->getOrderedConfigs(); + const size_t configCount = configs.size(); + for (size_t c = 0; c < configCount; c++) { + const DefaultKeyedVector<ConfigDescription, sp<Entry> >& configEntries = configs[c]->getEntries(); + const size_t configEntryCount = configEntries.size(); + for (size_t ce = 0; ce < configEntryCount; ce++) { + const ConfigDescription& config = configEntries.keyAt(ce); + if (AaptConfig::isDensityOnly(config)) { + // This configuration only varies with regards to density. + const Symbol symbol(mOrderedPackages[p]->getName(), + types[t]->getName(), + configs[c]->getName(), + getResId(mOrderedPackages[p], types[t], configs[c]->getEntryIndex())); + + const sp<Entry>& entry = configEntries.valueAt(ce); + AaptUtil::appendValue(resources, symbol, SymbolDefinition(symbol, config, entry->getPos())); + } + } + } } - node->removeAttribute(attrIndex); } - - return NO_ERROR; } diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h index eac5dd3..eef0ae1 100644 --- a/tools/aapt/ResourceTable.h +++ b/tools/aapt/ResourceTable.h @@ -7,15 +7,16 @@ #ifndef RESOURCE_TABLE_H #define RESOURCE_TABLE_H -#include "ConfigDescription.h" -#include "StringPool.h" -#include "SourcePos.h" -#include "ResourceFilter.h" - #include <map> #include <queue> #include <set> +#include "ConfigDescription.h" +#include "ResourceFilter.h" +#include "SourcePos.h" +#include "StringPool.h" +#include "Symbol.h" + using namespace std; class XMLNode; @@ -469,6 +470,14 @@ public: bool overlay = false, bool autoAddOverlay = false); + bool isPublic(const String16& entry) const { + return mPublic.indexOfKey(entry) >= 0; + } + + sp<ConfigList> removeEntry(const String16& entry); + + SortedVector<ConfigDescription> getUniqueConfigs() const; + const SourcePos& getFirstPublicSourcePos() const { return *mFirstPublicSourcePos; } int32_t getPublicIndex() const { return mPublicIndex; } @@ -478,19 +487,16 @@ public: status_t applyPublicEntryOrder(); - const SortedVector<ConfigDescription>& getUniqueConfigs() const { return mUniqueConfigs; } - const DefaultKeyedVector<String16, sp<ConfigList> >& getConfigs() const { return mConfigs; } const Vector<sp<ConfigList> >& getOrderedConfigs() const { return mOrderedConfigs; } - const SortedVector<String16>& getCanAddEntries() const { return mCanAddEntries; } const SourcePos& getPos() const { return mPos; } + private: String16 mName; SourcePos* mFirstPublicSourcePos; DefaultKeyedVector<String16, Public> mPublic; - SortedVector<ConfigDescription> mUniqueConfigs; DefaultKeyedVector<String16, sp<ConfigList> > mConfigs; Vector<sp<ConfigList> > mOrderedConfigs; SortedVector<String16> mCanAddEntries; @@ -526,6 +532,8 @@ public: const DefaultKeyedVector<String16, sp<Type> >& getTypes() const { return mTypes; } const Vector<sp<Type> >& getOrderedTypes() const { return mOrderedTypes; } + void movePrivateAttrs(); + private: status_t setStrings(const sp<AaptFile>& data, ResStringPool* strings, @@ -543,6 +551,8 @@ public: DefaultKeyedVector<String16, uint32_t> mKeyStringsMapping; }; + void getDensityVaryingResources(KeyedVector<Symbol, Vector<SymbolDefinition> >& resources); + private: void writePublicDefinitions(const String16& package, FILE* fp, bool pub); sp<Package> getPackage(const String16& package); @@ -565,7 +575,7 @@ private: const Item* getItem(uint32_t resID, uint32_t attrID) const; bool getItemValue(uint32_t resID, uint32_t attrID, Res_value* outValue); - bool isAttributeFromL(uint32_t attrId); + int getPublicAttributeSdkLevel(uint32_t attrId) const; String16 mAssetsPackage; diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h new file mode 100644 index 0000000..4e0fe10 --- /dev/null +++ b/tools/aapt/SdkConstants.h @@ -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. + */ + +#ifndef H_AAPT_SDK_CONSTANTS +#define H_AAPT_SDK_CONSTANTS + +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, +}; + +#endif // H_AAPT_SDK_CONSTANTS diff --git a/tools/aapt/SourcePos.cpp b/tools/aapt/SourcePos.cpp index ae25047..3864320 100644 --- a/tools/aapt/SourcePos.cpp +++ b/tools/aapt/SourcePos.cpp @@ -141,6 +141,12 @@ SourcePos::printf(const char* fmt, ...) const } bool +SourcePos::operator<(const SourcePos& rhs) const +{ + return (file < rhs.file) || (line < rhs.line); +} + +bool SourcePos::hasErrors() { return g_errors.size() > 0; diff --git a/tools/aapt/SourcePos.h b/tools/aapt/SourcePos.h index 4ce817f..13cfb9d 100644 --- a/tools/aapt/SourcePos.h +++ b/tools/aapt/SourcePos.h @@ -21,6 +21,8 @@ public: void warning(const char* fmt, ...) const; void printf(const char* fmt, ...) const; + bool operator<(const SourcePos& rhs) const; + static bool hasErrors(); static void printErrors(FILE* to); }; diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp index 06769e4..2727b3d 100644 --- a/tools/aapt/StringPool.cpp +++ b/tools/aapt/StringPool.cpp @@ -21,7 +21,8 @@ #define NOISY(x) //x -void strcpy16_htod(uint16_t* dst, const uint16_t* src) +#if __cplusplus >= 201103L +void strcpy16_htod(char16_t* dst, const char16_t* src) { while (*src) { char16_t s = htods(*src); @@ -30,6 +31,17 @@ void strcpy16_htod(uint16_t* dst, const uint16_t* src) } *dst = 0; } +#endif + +void strcpy16_htod(uint16_t* dst, const char16_t* src) +{ + while (*src) { + uint16_t s = htods(static_cast<uint16_t>(*src)); + *dst++ = s; + src++; + } + *dst = 0; +} void printStringPool(const ResStringPool* pool) { @@ -416,7 +428,7 @@ status_t StringPool::writeStringBlock(const sp<AaptFile>& pool) return NO_MEMORY; } - const size_t charSize = mUTF8 ? sizeof(uint8_t) : sizeof(char16_t); + const size_t charSize = mUTF8 ? sizeof(uint8_t) : sizeof(uint16_t); size_t strPos = 0; for (i=0; i<STRINGS; i++) { diff --git a/tools/aapt/StringPool.h b/tools/aapt/StringPool.h index 1b3abfd..a9c7bec 100644 --- a/tools/aapt/StringPool.h +++ b/tools/aapt/StringPool.h @@ -26,7 +26,10 @@ using namespace android; #define PRINT_STRING_METRICS 0 -void strcpy16_htod(uint16_t* dst, const uint16_t* src); +#if __cplusplus >= 201103L +void strcpy16_htod(char16_t* dst, const char16_t* src); +#endif +void strcpy16_htod(uint16_t* dst, const char16_t* src); void printStringPool(const ResStringPool* pool); diff --git a/tools/aapt/Symbol.h b/tools/aapt/Symbol.h new file mode 100644 index 0000000..e157541 --- /dev/null +++ b/tools/aapt/Symbol.h @@ -0,0 +1,95 @@ +/* + * 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. + */ + +#ifndef AAPT_SYMBOL_H +#define AAPT_SYMBOL_H + +#include <utils/String8.h> +#include <utils/String16.h> + +#include "ConfigDescription.h" +#include "SourcePos.h" + +/** + * A resource symbol, not attached to any configuration or context. + */ +struct Symbol { + inline Symbol(); + inline Symbol(const android::String16& p, const android::String16& t, const android::String16& n, uint32_t i); + inline android::String8 toString() const; + inline bool operator<(const Symbol& rhs) const; + + android::String16 package; + android::String16 type; + android::String16 name; + uint32_t id; + +}; + +/** + * A specific defintion of a symbol, defined with a configuration and a definition site. + */ +struct SymbolDefinition { + inline SymbolDefinition(); + inline SymbolDefinition(const Symbol& s, const ConfigDescription& c, const SourcePos& src); + inline bool operator<(const SymbolDefinition& rhs) const; + + Symbol symbol; + ConfigDescription config; + SourcePos source; +}; + +// +// Implementations +// + +Symbol::Symbol() { +} + +Symbol::Symbol(const android::String16& p, const android::String16& t, const android::String16& n, uint32_t i) + : package(p) + , type(t) + , name(n) + , id(i) { +} + +android::String8 Symbol::toString() const { + return android::String8::format("%s:%s/%s (0x%08x)", + android::String8(package).string(), + android::String8(type).string(), + android::String8(name).string(), + (int) id); +} + +bool Symbol::operator<(const Symbol& rhs) const { + return (package < rhs.package) || (type < rhs.type) || (name < rhs.name) || (id < rhs.id); +} + +SymbolDefinition::SymbolDefinition() { +} + +SymbolDefinition::SymbolDefinition(const Symbol& s, const ConfigDescription& c, const SourcePos& src) + : symbol(s) + , config(c) + , source(src) { +} + +bool SymbolDefinition::operator<(const SymbolDefinition& rhs) const { + return (symbol < rhs.symbol) || (config < rhs.config) || (source < rhs.source); +} + +#endif // AAPT_SYMBOL_H + diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp index 51a4154..899fb63 100644 --- a/tools/aapt/XMLNode.cpp +++ b/tools/aapt/XMLNode.cpp @@ -234,9 +234,9 @@ status_t parseStyledString(Bundle* bundle, const String8 element8(element16); size_t nslen; - const uint16_t* ns = inXml->getElementNamespace(&nslen); + const char16_t* ns = inXml->getElementNamespace(&nslen); if (ns == NULL) { - ns = (const uint16_t*)"\0\0"; + ns = (const char16_t*)"\0\0"; nslen = 0; } const String8 nspace(String16(ns, nslen)); @@ -291,9 +291,9 @@ moveon: } else if (code == ResXMLTree::END_TAG) { size_t nslen; - const uint16_t* ns = inXml->getElementNamespace(&nslen); + const char16_t* ns = inXml->getElementNamespace(&nslen); if (ns == NULL) { - ns = (const uint16_t*)"\0\0"; + ns = (const char16_t*)"\0\0"; nslen = 0; } const String8 nspace(String16(ns, nslen)); @@ -422,7 +422,7 @@ static String8 make_prefix(int depth) } static String8 build_namespace(const Vector<namespace_entry>& namespaces, - const uint16_t* ns) + const char16_t* ns) { String8 str; if (ns != NULL) { @@ -453,9 +453,9 @@ void printXMLBlock(ResXMLTree* block) int i; if (code == ResXMLTree::START_TAG) { size_t len; - const uint16_t* ns16 = block->getElementNamespace(&len); + const char16_t* ns16 = block->getElementNamespace(&len); String8 elemNs = build_namespace(namespaces, ns16); - const uint16_t* com16 = block->getComment(&len); + const char16_t* com16 = block->getComment(&len); if (com16) { printf("%s <!-- %s -->\n", prefix.string(), String8(com16).string()); } @@ -503,7 +503,7 @@ void printXMLBlock(ResXMLTree* block) } else if (code == ResXMLTree::START_NAMESPACE) { namespace_entry ns; size_t len; - const uint16_t* prefix16 = block->getNamespacePrefix(&len); + const char16_t* prefix16 = block->getNamespacePrefix(&len); if (prefix16) { ns.prefix = String8(prefix16); } else { @@ -518,7 +518,7 @@ void printXMLBlock(ResXMLTree* block) depth--; const namespace_entry& ns = namespaces.top(); size_t len; - const uint16_t* prefix16 = block->getNamespacePrefix(&len); + const char16_t* prefix16 = block->getNamespacePrefix(&len); String8 pr; if (prefix16) { pr = String8(prefix16); diff --git a/tools/layoutlib/bridge/src/android/text/format/DateFormat_Delegate.java b/tools/layoutlib/bridge/src/android/text/format/DateFormat_Delegate.java index 8cd1a69..1e4f213 100644 --- a/tools/layoutlib/bridge/src/android/text/format/DateFormat_Delegate.java +++ b/tools/layoutlib/bridge/src/android/text/format/DateFormat_Delegate.java @@ -34,4 +34,9 @@ public class DateFormat_Delegate { /*package*/ static boolean is24HourFormat(Context context) { return false; } + + @LayoutlibDelegate + /*package*/ static boolean is24HourFormat(Context context, int userHandle) { + return false; + } } diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index c403ce6..5176419 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -228,6 +228,11 @@ public class IWindowManagerImpl implements IWindowManager { } @Override + public void overridePendingAppTransitionInPlace(String packageName, int anim) { + // TODO Auto-generated method stub + } + + @Override public void pauseKeyDispatching(IBinder arg0) throws RemoteException { // TODO Auto-generated method stub diff --git a/tools/layoutlib/bridge/src/android/widget/TimePickerSpinnerDelegate_Delegate.java b/tools/layoutlib/bridge/src/android/widget/TimePickerClockDelegate_Delegate.java index c9d35b9..1bd9830 100644 --- a/tools/layoutlib/bridge/src/android/widget/TimePickerSpinnerDelegate_Delegate.java +++ b/tools/layoutlib/bridge/src/android/widget/TimePickerClockDelegate_Delegate.java @@ -21,16 +21,16 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.view.KeyEvent; /** - * Delegate used to provide new implementation of few methods in {@link TimePickerSpinnerDelegate}. + * Delegate used to provide new implementation of few methods in {@link TimePickerClockDelegate}. */ -public class TimePickerSpinnerDelegate_Delegate { +public class TimePickerClockDelegate_Delegate { - // Copied from TimePickerSpinnerDelegate. + // Copied from TimePickerClockDelegate. private static final int AM = 0; private static final int PM = 1; @LayoutlibDelegate - static int getAmOrPmKeyCode(TimePickerSpinnerDelegate tpsd, int amOrPm) { + static int getAmOrPmKeyCode(TimePickerClockDelegate tpcd, int amOrPm) { // We don't care about locales here. if (amOrPm == AM) { return KeyEvent.KEYCODE_A; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java index 89288bf..e4cbb2f 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java @@ -90,7 +90,7 @@ public final class BridgeContentProvider implements IContentProvider { @Override public ParcelFileDescriptor openFile( - String callingPackage, Uri arg0, String arg1, ICancellationSignal signal) + String callingPackage, Uri arg0, String arg1, ICancellationSignal signal, IBinder token) throws RemoteException, FileNotFoundException { // TODO Auto-generated method stub return null; 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 2adeb67..3441878 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,6 +16,7 @@ package com.android.layoutlib.bridge.android; +import android.os.IBinder; import com.android.annotations.Nullable; import com.android.ide.common.rendering.api.ILayoutPullParser; import com.android.ide.common.rendering.api.IProjectCallback; @@ -957,12 +958,24 @@ public final class BridgeContext extends Context { } @Override + public int checkPermission(String arg0, int arg1, int arg2, IBinder arg3) { + // pass + return 0; + } + + @Override public int checkUriPermission(Uri arg0, int arg1, int arg2, int arg3) { // pass return 0; } @Override + public int checkUriPermission(Uri arg0, int arg1, int arg2, int arg3, IBinder arg4) { + // pass + return 0; + } + + @Override public int checkUriPermission(Uri arg0, String arg1, String arg2, int arg3, int arg4, int arg5) { // pass 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 05a6fd6..39ebdfc 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 @@ -116,11 +116,6 @@ public class BridgePowerManager implements IPowerManager { } @Override - public void setMaximumScreenOffTimeoutFromDeviceAdmin(int arg0) throws RemoteException { - // pass for now. - } - - @Override public void setStayOnSetting(int arg0) throws RemoteException { // pass for now. } 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 997b199..4c4454d 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 @@ -95,6 +95,10 @@ public final class BridgeWindow implements IWindow { } @Override + public void dispatchWindowShown() { + } + + @Override public IBinder asBinder() { // pass for now. return null; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java index 0ed6ab1..0f51d00 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 @@ -38,7 +38,7 @@ import android.view.WindowManager.LayoutParams; public final class BridgeWindowSession implements IWindowSession { @Override - public int add(IWindow arg0, int seq, LayoutParams arg1, int arg2, Rect arg3, + public int add(IWindow arg0, int seq, LayoutParams arg1, int arg2, Rect arg3, Rect arg4, InputChannel outInputchannel) throws RemoteException { // pass for now. @@ -47,7 +47,7 @@ public final class BridgeWindowSession implements IWindowSession { @Override public int addToDisplay(IWindow arg0, int seq, LayoutParams arg1, int arg2, int displayId, - Rect arg3, InputChannel outInputchannel) + Rect arg3, Rect arg4, InputChannel outInputchannel) throws RemoteException { // pass for now. return 0; @@ -55,7 +55,7 @@ public final class BridgeWindowSession implements IWindowSession { @Override public int addWithoutInputChannel(IWindow arg0, int seq, LayoutParams arg1, int arg2, - Rect arg3) + Rect arg3, Rect arg4) throws RemoteException { // pass for now. return 0; @@ -63,7 +63,7 @@ public final class BridgeWindowSession implements IWindowSession { @Override public int addToDisplayWithoutInputChannel(IWindow arg0, int seq, LayoutParams arg1, int arg2, - int displayId, Rect arg3) + int displayId, Rect arg3, Rect arg4) throws RemoteException { // pass for now. return 0; diff --git a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java index 8898856..b8b5fed 100644 --- a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java +++ b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java @@ -223,7 +223,7 @@ public class ICU_Delegate { result.decimalSeparator = '.'; result.groupingSeparator = ','; result.patternSeparator = ' '; - result.percent = '%'; + result.percent = "%"; result.perMill = '\u2030'; result.monetarySeparator = ' '; result.minusSign = "-"; 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 96725af..a86fcdd 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 @@ -165,11 +165,27 @@ public class Main { if (!out.isDirectory()) { return null; } - File sdkDir = new File(out, "sdk" + File.separator + "sdk"); + File sdkDir = new File(out, "sdk"); if (!sdkDir.isDirectory()) { - // The directory we thought that should contain the sdk is not a directory. return null; } + File[] sdkDirs = sdkDir.listFiles(new FileFilter() { + @Override + public boolean accept(File path) { + // We need to search for $TARGET_PRODUCT (usually, sdk_phone_armv7) + return path.isDirectory() && path.getName().startsWith("sdk"); + } + }); + for (File dir : sdkDirs) { + String platformDir = getPlatformDirFromHostOutSdkSdk(dir); + if (platformDir != null) { + return platformDir; + } + } + return null; + } + + private static String getPlatformDirFromHostOutSdkSdk(File sdkDir) { File[] possibleSdks = sdkDir.listFiles(new FileFilter() { @Override public boolean accept(File path) { 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 f9e6151..98acd2f 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 @@ -20,6 +20,7 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import com.android.tools.layoutlib.java.AutoCloseable; import com.android.tools.layoutlib.java.Charsets; import com.android.tools.layoutlib.java.IntegralToString; +import com.android.tools.layoutlib.java.LinkedHashMap_Delegate; import com.android.tools.layoutlib.java.Objects; import com.android.tools.layoutlib.java.System_Delegate; import com.android.tools.layoutlib.java.UnsafeByteSequence; @@ -133,6 +134,7 @@ public final class CreateInfo implements ICreateInfo { UnsafeByteSequence.class, Charsets.class, System_Delegate.class, + LinkedHashMap_Delegate.class, }; /** @@ -170,7 +172,7 @@ public final class CreateInfo implements ICreateInfo { "android.view.RenderNode#nSetElevation", "android.view.RenderNode#nGetElevation", "android.view.ViewGroup#drawChild", - "android.widget.TimePickerSpinnerDelegate#getAmOrPmKeyCode", + "android.widget.TimePickerClockDelegate#getAmOrPmKeyCode", "com.android.internal.view.menu.MenuBuilder#createNewMenuItem", "com.android.internal.util.XmlUtils#convertValueToInt", "com.android.internal.textservice.ITextServicesManager$Stub#asInterface", 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 1e2623f..384d8ca 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 @@ -16,6 +16,7 @@ package com.android.tools.layoutlib.create; +import com.android.tools.layoutlib.java.LinkedHashMap_Delegate; import com.android.tools.layoutlib.java.System_Delegate; import org.objectweb.asm.ClassVisitor; @@ -26,8 +27,10 @@ import org.objectweb.asm.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; /** @@ -44,7 +47,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor { "([CI[CII)V", "([BI[BII)V", "([SI[SII)V", "([II[III)V", "([JI[JII)V", "([FI[FII)V", "([DI[DII)V", "([ZI[ZII)V")); - private static final List<MethodReplacer> METHOD_REPLACERS = new ArrayList<MethodReplacer>(2); + private static final List<MethodReplacer> METHOD_REPLACERS = new ArrayList<MethodReplacer>(5); private static final String ANDROID_LOCALE_CLASS = "com/android/layoutlib/bridge/android/AndroidLocale"; @@ -74,7 +77,8 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor { // Case 2: java.util.Locale.toLanguageTag() and java.util.Locale.getScript() METHOD_REPLACERS.add(new MethodReplacer() { - String LOCALE_TO_STRING = Type.getMethodDescriptor(STRING, Type.getType(Locale.class)); + private final String LOCALE_TO_STRING = + Type.getMethodDescriptor(STRING, Type.getType(Locale.class)); @Override public boolean isNeeded(String owner, String name, String desc) { @@ -129,6 +133,30 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor { mi.owner = Type.getInternalName(System_Delegate.class); } }); + + // Case 5: java.util.LinkedHashMap.eldest() + METHOD_REPLACERS.add(new MethodReplacer() { + + private final String VOID_TO_MAP_ENTRY = + Type.getMethodDescriptor(Type.getType(Map.Entry.class)); + private final String LINKED_HASH_MAP = Type.getInternalName(LinkedHashMap.class); + + @Override + public boolean isNeeded(String owner, String name, String desc) { + return LINKED_HASH_MAP.equals(owner) && + "eldest".equals(name) && + VOID_TO_MAP_ENTRY.equals(desc); + } + + @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)); + } + }); } public static boolean isReplacementNeeded(String owner, String name, String desc) { diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/LinkedHashMap_Delegate.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/LinkedHashMap_Delegate.java new file mode 100644 index 0000000..59cc75f --- /dev/null +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/LinkedHashMap_Delegate.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.layoutlib.java; + +import com.android.tools.layoutlib.create.ReplaceMethodCallsAdapter; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Provides alternate implementation to java.util.LinkedHashMap#eldest(), which is present as a + * non-public method in the Android VM, but not present on the host VM. This is injected in the + * layoutlib using {@link ReplaceMethodCallsAdapter}. + */ +public class LinkedHashMap_Delegate { + public static <K,V> Map.Entry<K,V> eldest(LinkedHashMap<K,V> map) { + Iterator<Entry<K, V>> iterator = map.entrySet().iterator(); + return iterator.hasNext() ? iterator.next() : null; + } +} diff --git a/tools/split-select/Abi.cpp b/tools/split-select/Abi.cpp new file mode 100644 index 0000000..180dd8f --- /dev/null +++ b/tools/split-select/Abi.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Abi.h" + +using namespace android; + +namespace split { +namespace abi { + +static Vector<Variant> buildVariants(Variant v1, Variant v2) { + Vector<Variant> v; + v.add(v1); + v.add(v2); + return v; +} + +static Vector<Variant> buildVariants(Variant v1, Variant v2, Variant v3) { + Vector<Variant> v; + v.add(v1); + v.add(v2); + v.add(v3); + return v; +} + +static const Vector<Variant> sNoneVariants; +static const Vector<Variant> sArmVariants = buildVariants(Variant_armeabi, Variant_armeabi_v7a, Variant_arm64_v8a); +static const Vector<Variant> sIntelVariants = buildVariants(Variant_x86, Variant_x86_64); +static const Vector<Variant> sMipsVariants = buildVariants(Variant_mips, Variant_mips64); + +Family getFamily(Variant variant) { + switch (variant) { + case Variant_none: + return Family_none; + case Variant_armeabi: + case Variant_armeabi_v7a: + case Variant_arm64_v8a: + return Family_arm; + case Variant_x86: + case Variant_x86_64: + return Family_intel; + case Variant_mips: + case Variant_mips64: + return Family_mips; + } + return Family_none; +} + +const Vector<Variant>& getVariants(Family family) { + switch (family) { + case Family_none: + return sNoneVariants; + case Family_arm: + return sArmVariants; + case Family_intel: + return sIntelVariants; + case Family_mips: + return sMipsVariants; + } + return sNoneVariants; +} + +const char* toString(Variant variant) { + switch (variant) { + case Variant_none: + return ""; + case Variant_armeabi: + return "armeabi"; + case Variant_armeabi_v7a: + return "armeabi-v7a"; + case Variant_arm64_v8a: + return "arm64-v8a"; + case Variant_x86: + return "x86"; + case Variant_x86_64: + return "x86_64"; + case Variant_mips: + return "mips"; + case Variant_mips64: + return "mips64"; + } + return ""; +} + +} // namespace abi +} // namespace split diff --git a/tools/split-select/Abi.h b/tools/split-select/Abi.h new file mode 100644 index 0000000..85b4d62 --- /dev/null +++ b/tools/split-select/Abi.h @@ -0,0 +1,50 @@ +/* + * 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. + */ + +#ifndef H_ANDROID_SPLIT_ABI +#define H_ANDROID_SPLIT_ABI + +#include <utils/Vector.h> + +namespace split { +namespace abi { + +enum Variant { + Variant_none = 0, + Variant_armeabi, + Variant_armeabi_v7a, + Variant_arm64_v8a, + Variant_x86, + Variant_x86_64, + Variant_mips, + Variant_mips64, +}; + +enum Family { + Family_none, + Family_arm, + Family_intel, + Family_mips, +}; + +Family getFamily(Variant variant); +const android::Vector<Variant>& getVariants(Family family); +const char* toString(Variant variant); + +} // namespace abi +} // namespace split + +#endif // H_ANDROID_SPLIT_ABI diff --git a/tools/split-select/Android.mk b/tools/split-select/Android.mk new file mode 100644 index 0000000..013e570 --- /dev/null +++ b/tools/split-select/Android.mk @@ -0,0 +1,117 @@ +# +# 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. +# + +# 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 := \ + Abi.cpp \ + Grouper.cpp \ + Rule.cpp \ + RuleGenerator.cpp \ + SplitDescription.cpp \ + SplitSelector.cpp + +testSources := \ + Grouper_test.cpp \ + Rule_test.cpp \ + RuleGenerator_test.cpp \ + SplitSelector_test.cpp \ + TestRules.cpp + +cIncludes := \ + external/zlib \ + frameworks/base/tools + +hostLdLibs := +hostStaticLibs := \ + libaapt \ + libandroidfw \ + libpng \ + liblog \ + libutils \ + libcutils \ + libexpat \ + libziparchive-host + +cFlags := -Wall -Werror + +ifeq ($(HOST_OS),linux) + hostLdLibs += -lrt -ldl -lpthread +endif + +# Statically link libz for MinGW (Win SDK under Linux), +# and dynamically link for all others. +ifneq ($(strip $(USE_MINGW)),) + hostStaticLibs += libz +else + hostLdLibs += -lz +endif + + +# ========================================================== +# Build the host static library: libsplit-select +# ========================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := libsplit-select + +LOCAL_SRC_FILES := $(sources) + +LOCAL_C_INCLUDES += $(cIncludes) +LOCAL_CFLAGS += $(cFlags) -D_DARWIN_UNLIMITED_STREAMS + +include $(BUILD_HOST_STATIC_LIBRARY) + + +# ========================================================== +# Build the host tests: libsplit-select_tests +# ========================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := libsplit-select_tests +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(testSources) + +LOCAL_C_INCLUDES += $(cIncludes) +LOCAL_STATIC_LIBRARIES += libsplit-select $(hostStaticLibs) +LOCAL_LDLIBS += $(hostLdLibs) +LOCAL_CFLAGS += $(cFlags) + +include $(BUILD_HOST_NATIVE_TEST) + +# ========================================================== +# Build the host executable: split-select +# ========================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := split-select + +LOCAL_SRC_FILES := $(main) + +LOCAL_C_INCLUDES += $(cIncludes) +LOCAL_STATIC_LIBRARIES += libsplit-select $(hostStaticLibs) +LOCAL_LDLIBS += $(hostLdLibs) +LOCAL_CFLAGS += $(cFlags) + +include $(BUILD_HOST_EXECUTABLE) + +endif # No TARGET_BUILD_APPS or TARGET_BUILD_PDK diff --git a/tools/split-select/Grouper.cpp b/tools/split-select/Grouper.cpp new file mode 100644 index 0000000..22685cd --- /dev/null +++ b/tools/split-select/Grouper.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Grouper.h" + +#include "aapt/AaptUtil.h" +#include "SplitDescription.h" + +#include <utils/KeyedVector.h> +#include <utils/Vector.h> + +using namespace android; +using AaptUtil::appendValue; + +namespace split { + +Vector<SortedVector<SplitDescription> > +groupByMutualExclusivity(const Vector<SplitDescription>& splits) { + Vector<SortedVector<SplitDescription> > groups; + + // 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]; + if (split.config.density != 0) { + SplitDescription key(split); + key.config.density = 0; + key.config.sdkVersion = 0; // Ignore density so we can support anydpi. + appendValue(densityGroups, key, split); + } else if (split.abi != abi::Variant_none) { + 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); + } + } + + const size_t densityCount = densityGroups.size(); + for (size_t i = 0; i < densityCount; i++) { + groups.add(densityGroups[i]); + } + + const size_t abiCount = abiGroups.size(); + for (size_t i = 0; i < abiCount; i++) { + groups.add(abiGroups[i]); + } + + const size_t localeCount = localeGroups.size(); + for (size_t i = 0; i < localeCount; i++) { + groups.add(localeGroups[i]); + } + return groups; +} + +} // namespace split diff --git a/tools/split-select/Grouper.h b/tools/split-select/Grouper.h new file mode 100644 index 0000000..5cb0b5b --- /dev/null +++ b/tools/split-select/Grouper.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef H_ANDROID_SPLIT_GROUPER +#define H_ANDROID_SPLIT_GROUPER + +#include "SplitDescription.h" + +#include <utils/SortedVector.h> +#include <utils/Vector.h> + +namespace split { + +android::Vector<android::SortedVector<SplitDescription> > +groupByMutualExclusivity(const android::Vector<SplitDescription>& splits); + +} // namespace split + +#endif // H_ANDROID_SPLIT_GROUPER diff --git a/tools/split-select/Grouper_test.cpp b/tools/split-select/Grouper_test.cpp new file mode 100644 index 0000000..a5f9c5a --- /dev/null +++ b/tools/split-select/Grouper_test.cpp @@ -0,0 +1,185 @@ +/* + * 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. + */ + +#include "Grouper.h" + +#include "SplitDescription.h" + +#include <gtest/gtest.h> +#include <utils/String8.h> +#include <utils/Vector.h> + +using namespace android; + +namespace split { + +class GrouperTest : public ::testing::Test { +protected: + virtual void SetUp() { + Vector<SplitDescription> splits; + addSplit(splits, "en-rUS-sw600dp-hdpi"); + addSplit(splits, "fr-rFR-sw600dp-hdpi"); + addSplit(splits, "fr-rFR-sw600dp-xhdpi"); + addSplit(splits, ":armeabi"); + addSplit(splits, "en-rUS-sw300dp-xhdpi"); + addSplit(splits, "large"); + addSplit(splits, "pl-rPL"); + addSplit(splits, "xlarge"); + addSplit(splits, "en-rUS-sw600dp-xhdpi"); + addSplit(splits, "en-rUS-sw300dp-hdpi"); + addSplit(splits, "xxhdpi"); + addSplit(splits, "hdpi"); + addSplit(splits, "de-rDE"); + addSplit(splits, "xhdpi"); + addSplit(splits, ":x86"); + addSplit(splits, "anydpi"); + addSplit(splits, "v7"); + addSplit(splits, "v8"); + addSplit(splits, "sw600dp"); + addSplit(splits, "sw300dp"); + mGroups = groupByMutualExclusivity(splits); + } + + void addSplit(Vector<SplitDescription>& splits, const char* str); + void expectHasGroupWithSplits(const char* a); + void expectHasGroupWithSplits(const char* a, const char* b); + void expectHasGroupWithSplits(const char* a, const char* b, const char* c); + void expectHasGroupWithSplits(const char* a, const char* b, const char* c, const char* d); + void expectHasGroupWithSplits(const Vector<const char*>& expectedStrs); + + Vector<SortedVector<SplitDescription> > mGroups; +}; + +TEST_F(GrouperTest, shouldHaveCorrectNumberOfGroups) { + EXPECT_EQ(12u, mGroups.size()); +} + +TEST_F(GrouperTest, shouldGroupDensities) { + expectHasGroupWithSplits("en-rUS-sw300dp-hdpi", "en-rUS-sw300dp-xhdpi"); + expectHasGroupWithSplits("en-rUS-sw600dp-hdpi", "en-rUS-sw600dp-xhdpi"); + expectHasGroupWithSplits("fr-rFR-sw600dp-hdpi", "fr-rFR-sw600dp-xhdpi"); + expectHasGroupWithSplits("hdpi", "xhdpi", "xxhdpi", "anydpi"); +} + +TEST_F(GrouperTest, shouldGroupAbi) { + expectHasGroupWithSplits(":armeabi", ":x86"); +} + +TEST_F(GrouperTest, shouldGroupLocale) { + expectHasGroupWithSplits("pl-rPL", "de-rDE"); +} + +TEST_F(GrouperTest, shouldGroupEachSplitIntoItsOwnGroup) { + expectHasGroupWithSplits("large"); + expectHasGroupWithSplits("xlarge"); + expectHasGroupWithSplits("v7"); + expectHasGroupWithSplits("v8"); + expectHasGroupWithSplits("sw600dp"); + expectHasGroupWithSplits("sw300dp"); +} + +// +// Helper methods +// + +void GrouperTest::expectHasGroupWithSplits(const char* a) { + Vector<const char*> expected; + expected.add(a); + expectHasGroupWithSplits(expected); +} + +void GrouperTest::expectHasGroupWithSplits(const char* a, const char* b) { + Vector<const char*> expected; + expected.add(a); + expected.add(b); + expectHasGroupWithSplits(expected); +} + +void GrouperTest::expectHasGroupWithSplits(const char* a, const char* b, const char* c) { + Vector<const char*> expected; + expected.add(a); + expected.add(b); + expected.add(c); + expectHasGroupWithSplits(expected); +} + +void GrouperTest::expectHasGroupWithSplits(const char* a, const char* b, const char* c, const char* d) { + Vector<const char*> expected; + expected.add(a); + expected.add(b); + expected.add(c); + expected.add(d); + expectHasGroupWithSplits(expected); +} + +void GrouperTest::expectHasGroupWithSplits(const Vector<const char*>& expectedStrs) { + Vector<SplitDescription> splits; + const size_t expectedStrCount = expectedStrs.size(); + for (size_t i = 0; i < expectedStrCount; i++) { + splits.add(); + if (!SplitDescription::parse(String8(expectedStrs[i]), &splits.editTop())) { + ADD_FAILURE() << "Failed to parse SplitDescription " << expectedStrs[i]; + return; + } + } + const size_t splitCount = splits.size(); + + const size_t groupCount = mGroups.size(); + for (size_t i = 0; i < groupCount; i++) { + const SortedVector<SplitDescription>& group = mGroups[i]; + if (group.size() != splitCount) { + continue; + } + + size_t found = 0; + for (size_t j = 0; j < splitCount; j++) { + if (group.indexOf(splits[j]) >= 0) { + found++; + } + } + + if (found == splitCount) { + return; + } + } + + String8 errorMessage("Failed to find expected group ["); + for (size_t i = 0; i < splitCount; i++) { + if (i != 0) { + errorMessage.append(", "); + } + errorMessage.append(splits[i].toString()); + } + errorMessage.append("].\nActual:\n"); + + for (size_t i = 0; i < groupCount; i++) { + errorMessage.appendFormat("Group %d:\n", int(i + 1)); + const SortedVector<SplitDescription>& group = mGroups[i]; + for (size_t j = 0; j < group.size(); j++) { + errorMessage.append(" "); + errorMessage.append(group[j].toString()); + errorMessage.append("\n"); + } + } + ADD_FAILURE() << errorMessage.string(); +} + +void GrouperTest::addSplit(Vector<SplitDescription>& splits, const char* str) { + splits.add(); + EXPECT_TRUE(SplitDescription::parse(String8(str), &splits.editTop())); +} + +} // namespace split diff --git a/tools/split-select/Main.cpp b/tools/split-select/Main.cpp new file mode 100644 index 0000000..d3eb012 --- /dev/null +++ b/tools/split-select/Main.cpp @@ -0,0 +1,376 @@ +/* + * 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. + */ + +#include <algorithm> +#include <cstdio> + +#include "aapt/AaptUtil.h" + +#include "Grouper.h" +#include "Rule.h" +#include "RuleGenerator.h" +#include "SplitDescription.h" +#include "SplitSelector.h" + +#include <androidfw/AssetManager.h> +#include <androidfw/ResourceTypes.h> +#include <utils/KeyedVector.h> +#include <utils/Vector.h> + +using namespace android; + +namespace split { + +static void usage() { + fprintf(stderr, + "split-select --help\n" + "split-select --target <config> --base <path/to/apk> [--split <path/to/apk> [...]]\n" + "split-select --generate --base <path/to/apk> [--split <path/to/apk> [...]]\n" + "\n" + " --help Displays more information about this program.\n" + " --target <config> Performs the Split APK selection on the given configuration.\n" + " --generate Generates the logic for selecting the Split APK, in JSON format.\n" + " --base <path/to/apk> Specifies the base APK, from which all Split APKs must be based off.\n" + " --split <path/to/apk> Includes a Split APK in the selection process.\n" + "\n" + " Where <config> is an extended AAPT resource qualifier of the form\n" + " 'resource-qualifiers:extended-qualifiers', where 'resource-qualifiers' is an AAPT resource\n" + " qualifier (ex: en-rUS-sw600dp-xhdpi), and 'extended-qualifiers' is an ordered list of one\n" + " qualifier (or none) from each category:\n" + " Architecture: armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, mips\n"); +} + +static void help() { + usage(); + fprintf(stderr, "\n" + " Generates the logic for selecting a Split APK given some target Android device configuration.\n" + " Using the flag --generate will emit a JSON encoded tree of rules that must be satisfied in order\n" + " to install the given Split APK. Using the flag --target along with the device configuration\n" + " will emit the set of Split APKs to install, following the same logic that would have been emitted\n" + " via JSON.\n"); +} + +Vector<SplitDescription> select(const SplitDescription& target, const Vector<SplitDescription>& splits) { + const SplitSelector selector(splits); + return selector.getBestSplits(target); +} + +void generate(const KeyedVector<String8, Vector<SplitDescription> >& splits, const String8& base) { + Vector<SplitDescription> allSplits; + const size_t apkSplitCount = splits.size(); + for (size_t i = 0; i < apkSplitCount; i++) { + allSplits.appendVector(splits[i]); + } + const SplitSelector selector(allSplits); + KeyedVector<SplitDescription, sp<Rule> > rules(selector.getRules()); + + bool first = true; + fprintf(stdout, "[\n"); + for (size_t i = 0; i < apkSplitCount; i++) { + if (splits.keyAt(i) == base) { + // Skip the base. + continue; + } + + if (!first) { + fprintf(stdout, ",\n"); + } + first = false; + + sp<Rule> masterRule = new Rule(); + masterRule->op = Rule::OR_SUBRULES; + const Vector<SplitDescription>& splitDescriptions = splits[i]; + const size_t splitDescriptionCount = splitDescriptions.size(); + for (size_t j = 0; j < splitDescriptionCount; j++) { + masterRule->subrules.add(rules.valueFor(splitDescriptions[j])); + } + masterRule = Rule::simplify(masterRule); + fprintf(stdout, " {\n \"path\": \"%s\",\n \"rules\": %s\n }", + splits.keyAt(i).string(), + masterRule->toJson(2).string()); + } + fprintf(stdout, "\n]\n"); +} + +static void removeRuntimeQualifiers(ConfigDescription* outConfig) { + outConfig->imsi = 0; + outConfig->orientation = ResTable_config::ORIENTATION_ANY; + outConfig->screenWidth = ResTable_config::SCREENWIDTH_ANY; + outConfig->screenHeight = ResTable_config::SCREENHEIGHT_ANY; + outConfig->uiMode &= ResTable_config::UI_MODE_NIGHT_ANY; +} + +struct AppInfo { + int versionCode; + int minSdkVersion; + bool multiArch; +}; + +static bool getAppInfo(const String8& path, AppInfo& outInfo) { + memset(&outInfo, 0, sizeof(outInfo)); + + AssetManager assetManager; + int32_t cookie = 0; + if (!assetManager.addAssetPath(path, &cookie)) { + return false; + } + + Asset* asset = assetManager.openNonAsset(cookie, "AndroidManifest.xml", Asset::ACCESS_BUFFER); + if (asset == NULL) { + return false; + } + + ResXMLTree xml; + if (xml.setTo(asset->getBuffer(true), asset->getLength(), false) != NO_ERROR) { + delete asset; + return false; + } + + const String16 kAndroidNamespace("http://schemas.android.com/apk/res/android"); + const String16 kManifestTag("manifest"); + const String16 kApplicationTag("application"); + const String16 kUsesSdkTag("uses-sdk"); + const String16 kVersionCodeAttr("versionCode"); + const String16 kMultiArchAttr("multiArch"); + const String16 kMinSdkVersionAttr("minSdkVersion"); + + ResXMLParser::event_code_t event; + while ((event = xml.next()) != ResXMLParser::BAD_DOCUMENT && + event != ResXMLParser::END_DOCUMENT) { + if (event != ResXMLParser::START_TAG) { + continue; + } + + size_t len; + const char16_t* name = xml.getElementName(&len); + String16 name16(name, len); + if (name16 == kManifestTag) { + ssize_t idx = xml.indexOfAttribute( + kAndroidNamespace.string(), kAndroidNamespace.size(), + kVersionCodeAttr.string(), kVersionCodeAttr.size()); + if (idx >= 0) { + outInfo.versionCode = xml.getAttributeData(idx); + } + + } else if (name16 == kApplicationTag) { + ssize_t idx = xml.indexOfAttribute( + kAndroidNamespace.string(), kAndroidNamespace.size(), + kMultiArchAttr.string(), kMultiArchAttr.size()); + if (idx >= 0) { + outInfo.multiArch = xml.getAttributeData(idx) != 0; + } + + } else if (name16 == kUsesSdkTag) { + ssize_t idx = xml.indexOfAttribute( + kAndroidNamespace.string(), kAndroidNamespace.size(), + kMinSdkVersionAttr.string(), kMinSdkVersionAttr.size()); + if (idx >= 0) { + uint16_t type = xml.getAttributeDataType(idx); + if (type >= Res_value::TYPE_FIRST_INT && type <= Res_value::TYPE_LAST_INT) { + outInfo.minSdkVersion = xml.getAttributeData(idx); + } else if (type == Res_value::TYPE_STRING) { + String8 minSdk8(xml.getStrings().string8ObjectAt(idx)); + char* endPtr; + int minSdk = strtol(minSdk8.string(), &endPtr, 10); + if (endPtr != minSdk8.string() + minSdk8.size()) { + fprintf(stderr, "warning: failed to parse android:minSdkVersion '%s'\n", + minSdk8.string()); + } else { + outInfo.minSdkVersion = minSdk; + } + } else { + fprintf(stderr, "warning: unrecognized value for android:minSdkVersion.\n"); + } + } + } + } + + delete asset; + return true; +} + +static Vector<SplitDescription> extractSplitDescriptionsFromApk(const String8& path) { + AssetManager assetManager; + Vector<SplitDescription> splits; + int32_t cookie = 0; + if (!assetManager.addAssetPath(path, &cookie)) { + return splits; + } + + const ResTable& res = assetManager.getResources(false); + if (res.getError() == NO_ERROR) { + Vector<ResTable_config> configs; + res.getConfigurations(&configs, true); + const size_t configCount = configs.size(); + for (size_t i = 0; i < configCount; i++) { + splits.add(); + splits.editTop().config = configs[i]; + } + } + + AssetDir* dir = assetManager.openNonAssetDir(cookie, "lib"); + if (dir != NULL) { + const size_t fileCount = dir->getFileCount(); + for (size_t i = 0; i < fileCount; i++) { + splits.add(); + Vector<String8> parts = AaptUtil::splitAndLowerCase(dir->getFileName(i), '-'); + if (parseAbi(parts, 0, &splits.editTop()) < 0) { + fprintf(stderr, "Malformed library %s\n", dir->getFileName(i).string()); + splits.pop(); + } + } + delete dir; + } + return splits; +} + +static int main(int argc, char** argv) { + // Skip over the first argument. + argc--; + argv++; + + bool generateFlag = false; + String8 targetConfigStr; + Vector<String8> splitApkPaths; + String8 baseApkPath; + while (argc > 0) { + const String8 arg(*argv); + if (arg == "--target") { + argc--; + argv++; + if (argc < 1) { + fprintf(stderr, "error: missing parameter for --target.\n"); + usage(); + return 1; + } + targetConfigStr.setTo(*argv); + } else if (arg == "--split") { + argc--; + argv++; + if (argc < 1) { + fprintf(stderr, "error: missing parameter for --split.\n"); + usage(); + return 1; + } + splitApkPaths.add(String8(*argv)); + } else if (arg == "--base") { + argc--; + argv++; + if (argc < 1) { + fprintf(stderr, "error: missing parameter for --base.\n"); + usage(); + return 1; + } + + if (baseApkPath.size() > 0) { + fprintf(stderr, "error: multiple --base flags not allowed.\n"); + usage(); + return 1; + } + baseApkPath.setTo(*argv); + } else if (arg == "--generate") { + generateFlag = true; + } else if (arg == "--help") { + help(); + return 0; + } else { + fprintf(stderr, "error: unknown argument '%s'.\n", arg.string()); + usage(); + return 1; + } + argc--; + argv++; + } + + if (!generateFlag && targetConfigStr == "") { + usage(); + return 1; + } + + if (baseApkPath.size() == 0) { + fprintf(stderr, "error: missing --base argument.\n"); + usage(); + return 1; + } + + // Find out some details about the base APK. + AppInfo baseAppInfo; + if (!getAppInfo(baseApkPath, baseAppInfo)) { + fprintf(stderr, "error: unable to read base APK: '%s'.\n", baseApkPath.string()); + return 1; + } + + SplitDescription targetSplit; + if (!generateFlag) { + if (!SplitDescription::parse(targetConfigStr, &targetSplit)) { + fprintf(stderr, "error: invalid --target config: '%s'.\n", + targetConfigStr.string()); + usage(); + return 1; + } + + // We don't want to match on things that will change at run-time + // (orientation, w/h, etc.). + removeRuntimeQualifiers(&targetSplit.config); + } + + splitApkPaths.add(baseApkPath); + + KeyedVector<String8, Vector<SplitDescription> > apkPathSplitMap; + KeyedVector<SplitDescription, String8> splitApkPathMap; + Vector<SplitDescription> splitConfigs; + const size_t splitCount = splitApkPaths.size(); + for (size_t i = 0; i < splitCount; i++) { + Vector<SplitDescription> splits = extractSplitDescriptionsFromApk(splitApkPaths[i]); + if (splits.isEmpty()) { + fprintf(stderr, "error: invalid --split path: '%s'. No splits found.\n", + splitApkPaths[i].string()); + usage(); + return 1; + } + apkPathSplitMap.replaceValueFor(splitApkPaths[i], splits); + const size_t apkSplitDescriptionCount = splits.size(); + for (size_t j = 0; j < apkSplitDescriptionCount; j++) { + splitApkPathMap.replaceValueFor(splits[j], splitApkPaths[i]); + } + splitConfigs.appendVector(splits); + } + + if (!generateFlag) { + Vector<SplitDescription> matchingConfigs = select(targetSplit, splitConfigs); + const size_t matchingConfigCount = matchingConfigs.size(); + SortedVector<String8> matchingSplitPaths; + for (size_t i = 0; i < matchingConfigCount; i++) { + matchingSplitPaths.add(splitApkPathMap.valueFor(matchingConfigs[i])); + } + + const size_t matchingSplitApkPathCount = matchingSplitPaths.size(); + for (size_t i = 0; i < matchingSplitApkPathCount; i++) { + if (matchingSplitPaths[i] != baseApkPath) { + fprintf(stdout, "%s\n", matchingSplitPaths[i].string()); + } + } + } else { + generate(apkPathSplitMap, baseApkPath); + } + return 0; +} + +} // namespace split + +int main(int argc, char** argv) { + return split::main(argc, argv); +} diff --git a/tools/split-select/Rule.cpp b/tools/split-select/Rule.cpp new file mode 100644 index 0000000..48d21ff --- /dev/null +++ b/tools/split-select/Rule.cpp @@ -0,0 +1,206 @@ +/* + * 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. + */ + +#include "Rule.h" + +#include <utils/String8.h> + +using namespace android; + +namespace split { + +inline static void indentStr(String8& str, int indent) { + while (indent > 0) { + str.append(" "); + indent--; + } +} + +Rule::Rule(const Rule& rhs) + : RefBase() + , op(rhs.op) + , key(rhs.key) + , negate(rhs.negate) + , stringArgs(rhs.stringArgs) + , longArgs(rhs.longArgs) + , subrules(rhs.subrules) { +} + +String8 Rule::toJson(int indent) const { + String8 str; + indentStr(str, indent); + str.append("{\n"); + indent++; + indentStr(str, indent); + str.append("\"op\": \""); + switch (op) { + case ALWAYS_TRUE: + str.append("ALWAYS_TRUE"); + break; + case GREATER_THAN: + str.append("GREATER_THAN"); + break; + case LESS_THAN: + str.append("LESS_THAN"); + break; + case EQUALS: + str.append("EQUALS"); + break; + case AND_SUBRULES: + str.append("AND_SUBRULES"); + break; + case OR_SUBRULES: + str.append("OR_SUBRULES"); + break; + case CONTAINS_ANY: + str.append("CONTAINS_ANY"); + break; + default: + str.appendFormat("%d", op); + break; + } + str.append("\""); + + if (negate) { + str.append(",\n"); + indentStr(str, indent); + str.append("\"negate\": true"); + } + + bool includeKey = true; + switch (op) { + case AND_SUBRULES: + case OR_SUBRULES: + includeKey = false; + break; + default: + break; + } + + if (includeKey) { + str.append(",\n"); + indentStr(str, indent); + str.append("\"property\": \""); + switch (key) { + case NONE: + str.append("NONE"); + break; + case SDK_VERSION: + str.append("SDK_VERSION"); + break; + case SCREEN_DENSITY: + str.append("SCREEN_DENSITY"); + break; + case NATIVE_PLATFORM: + str.append("NATIVE_PLATFORM"); + break; + case LANGUAGE: + str.append("LANGUAGE"); + break; + default: + str.appendFormat("%d", key); + break; + } + str.append("\""); + } + + if (op == AND_SUBRULES || op == OR_SUBRULES) { + str.append(",\n"); + indentStr(str, indent); + str.append("\"subrules\": [\n"); + const size_t subruleCount = subrules.size(); + for (size_t i = 0; i < subruleCount; i++) { + str.append(subrules[i]->toJson(indent + 1)); + if (i != subruleCount - 1) { + str.append(","); + } + str.append("\n"); + } + indentStr(str, indent); + str.append("]"); + } else { + switch (key) { + case SDK_VERSION: + case SCREEN_DENSITY: { + str.append(",\n"); + indentStr(str, indent); + str.append("\"args\": ["); + const size_t argCount = longArgs.size(); + for (size_t i = 0; i < argCount; i++) { + if (i != 0) { + str.append(", "); + } + str.appendFormat("%d", longArgs[i]); + } + str.append("]"); + break; + } + case LANGUAGE: + case NATIVE_PLATFORM: { + str.append(",\n"); + indentStr(str, indent); + str.append("\"args\": ["); + const size_t argCount = stringArgs.size(); + for (size_t i = 0; i < argCount; i++) { + if (i != 0) { + str.append(", "); + } + str.append(stringArgs[i]); + } + str.append("]"); + break; + } + default: + break; + } + } + str.append("\n"); + indent--; + indentStr(str, indent); + str.append("}"); + return str; +} + +sp<Rule> Rule::simplify(sp<Rule> rule) { + if (rule->op != AND_SUBRULES && rule->op != OR_SUBRULES) { + return rule; + } + + Vector<sp<Rule> > newSubrules; + newSubrules.setCapacity(rule->subrules.size()); + const size_t subruleCount = rule->subrules.size(); + for (size_t i = 0; i < subruleCount; i++) { + sp<Rule> simplifiedRule = simplify(rule->subrules.editItemAt(i)); + if (simplifiedRule != NULL) { + if (simplifiedRule->op == rule->op) { + newSubrules.appendVector(simplifiedRule->subrules); + } else { + newSubrules.add(simplifiedRule); + } + } + } + + const size_t newSubruleCount = newSubrules.size(); + if (newSubruleCount == 0) { + return NULL; + } else if (subruleCount == 1) { + return newSubrules.editTop(); + } + rule->subrules = newSubrules; + return rule; +} + +} // namespace split diff --git a/tools/split-select/Rule.h b/tools/split-select/Rule.h new file mode 100644 index 0000000..08a2075 --- /dev/null +++ b/tools/split-select/Rule.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef H_ANDROID_SPLIT_RULE +#define H_ANDROID_SPLIT_RULE + +#include "SplitDescription.h" + +#include <utils/RefBase.h> +#include <utils/StrongPointer.h> +#include <utils/String8.h> +#include <utils/Vector.h> + +namespace split { + +struct Rule : public virtual android::RefBase { + inline Rule(); + Rule(const Rule& rhs); + + enum Operator { + LESS_THAN = 1, + GREATER_THAN, + EQUALS, + CONTAINS_ANY, + CONTAINS_ALL, + IS_TRUE, + IS_FALSE, + AND_SUBRULES, + OR_SUBRULES, + ALWAYS_TRUE, + }; + + Operator op; + + enum Key { + NONE = 0, + SDK_VERSION, + SCREEN_DENSITY, + LANGUAGE, + NATIVE_PLATFORM, + TOUCH_SCREEN, + SCREEN_SIZE, + SCREEN_LAYOUT, + }; + + Key key; + bool negate; + + android::Vector<android::String8> stringArgs; + android::Vector<int> longArgs; + android::Vector<double> doubleArgs; + android::Vector<android::sp<Rule> > subrules; + + android::String8 toJson(int indent=0) const; + + static android::sp<Rule> simplify(android::sp<Rule> rule); +}; + +Rule::Rule() +: op(ALWAYS_TRUE) +, key(NONE) +, negate(false) {} + +} // namespace split + +#endif // H_ANDROID_SPLIT_RULE diff --git a/tools/split-select/RuleGenerator.cpp b/tools/split-select/RuleGenerator.cpp new file mode 100644 index 0000000..83c9795 --- /dev/null +++ b/tools/split-select/RuleGenerator.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "RuleGenerator.h" +#include "aapt/SdkConstants.h" + +#include <algorithm> +#include <cmath> +#include <vector> +#include <androidfw/ResourceTypes.h> + +using namespace android; + +namespace split { + +// Calculate the point at which the density selection changes between l and h. +static inline int findMid(int l, int h) { + double root = sqrt((h*h) + (8*l*h)); + return (double(-h) + root) / 2.0; +} + +sp<Rule> RuleGenerator::generateDensity(const Vector<int>& allDensities, size_t index) { + if (allDensities[index] != ResTable_config::DENSITY_ANY) { + sp<Rule> densityRule = new Rule(); + densityRule->op = Rule::AND_SUBRULES; + + const bool hasAnyDensity = std::find(allDensities.begin(), + allDensities.end(), (int) ResTable_config::DENSITY_ANY) != allDensities.end(); + + if (hasAnyDensity) { + sp<Rule> version = new Rule(); + version->op = Rule::LESS_THAN; + version->key = Rule::SDK_VERSION; + version->longArgs.add((long) SDK_LOLLIPOP); + densityRule->subrules.add(version); + } + + if (index > 0) { + sp<Rule> gt = new Rule(); + gt->op = Rule::GREATER_THAN; + gt->key = Rule::SCREEN_DENSITY; + gt->longArgs.add(findMid(allDensities[index - 1], allDensities[index]) - 1); + densityRule->subrules.add(gt); + } + + if (index + 1 < allDensities.size() && allDensities[index + 1] != ResTable_config::DENSITY_ANY) { + sp<Rule> lt = new Rule(); + lt->op = Rule::LESS_THAN; + lt->key = Rule::SCREEN_DENSITY; + lt->longArgs.add(findMid(allDensities[index], allDensities[index + 1])); + densityRule->subrules.add(lt); + } + return densityRule; + } else { + // SDK_VERSION is handled elsewhere, so we always pick DENSITY_ANY if it's + // available. + sp<Rule> always = new Rule(); + always->op = Rule::ALWAYS_TRUE; + return always; + } +} + +sp<Rule> RuleGenerator::generateAbi(const Vector<abi::Variant>& splitAbis, size_t index) { + const abi::Variant thisAbi = splitAbis[index]; + const Vector<abi::Variant>& familyVariants = abi::getVariants(abi::getFamily(thisAbi)); + + Vector<abi::Variant>::const_iterator start = + std::find(familyVariants.begin(), familyVariants.end(), thisAbi); + + Vector<abi::Variant>::const_iterator end = familyVariants.end(); + if (index + 1 < splitAbis.size()) { + end = std::find(start, familyVariants.end(), splitAbis[index + 1]); + } + + sp<Rule> abiRule = new Rule(); + abiRule->op = Rule::CONTAINS_ANY; + abiRule->key = Rule::NATIVE_PLATFORM; + while (start != end) { + abiRule->stringArgs.add(String8(abi::toString(*start))); + ++start; + } + return abiRule; +} + +sp<Rule> RuleGenerator::generate(const SortedVector<SplitDescription>& group, size_t index) { + sp<Rule> rootRule = new Rule(); + rootRule->op = Rule::AND_SUBRULES; + + if (group[index].config.locale != 0) { + sp<Rule> locale = new Rule(); + locale->op = Rule::EQUALS; + locale->key = Rule::LANGUAGE; + char str[RESTABLE_MAX_LOCALE_LEN]; + group[index].config.getBcp47Locale(str); + locale->stringArgs.add(String8(str)); + rootRule->subrules.add(locale); + } + + if (group[index].config.sdkVersion != 0) { + sp<Rule> sdk = new Rule(); + sdk->op = Rule::GREATER_THAN; + sdk->key = Rule::SDK_VERSION; + sdk->longArgs.add(group[index].config.sdkVersion - 1); + rootRule->subrules.add(sdk); + } + + if (group[index].config.density != 0) { + size_t densityIndex = 0; + Vector<int> allDensities; + allDensities.add(group[index].config.density); + + const size_t groupSize = group.size(); + for (size_t i = 0; i < groupSize; i++) { + if (group[i].config.density != group[index].config.density) { + // This group differs by density. + allDensities.clear(); + for (size_t j = 0; j < groupSize; j++) { + allDensities.add(group[j].config.density); + } + densityIndex = index; + break; + } + } + rootRule->subrules.add(generateDensity(allDensities, densityIndex)); + } + + if (group[index].abi != abi::Variant_none) { + size_t abiIndex = 0; + Vector<abi::Variant> allVariants; + allVariants.add(group[index].abi); + + const size_t groupSize = group.size(); + for (size_t i = 0; i < groupSize; i++) { + if (group[i].abi != group[index].abi) { + // This group differs by ABI. + allVariants.clear(); + for (size_t j = 0; j < groupSize; j++) { + allVariants.add(group[j].abi); + } + abiIndex = index; + break; + } + } + rootRule->subrules.add(generateAbi(allVariants, abiIndex)); + } + + return rootRule; +} + +} // namespace split diff --git a/tools/split-select/RuleGenerator.h b/tools/split-select/RuleGenerator.h new file mode 100644 index 0000000..619acd9 --- /dev/null +++ b/tools/split-select/RuleGenerator.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef H_ANDROID_SPLIT_RULE_GENERATOR +#define H_ANDROID_SPLIT_RULE_GENERATOR + +#include "Abi.h" +#include "Rule.h" +#include "SplitDescription.h" + +#include <utils/SortedVector.h> +#include <utils/Vector.h> + +namespace split { + +struct RuleGenerator { + // Generate rules for a Split given the group of mutually exclusive splits it belongs to + static android::sp<Rule> generate(const android::SortedVector<SplitDescription>& group, size_t index); + + static android::sp<Rule> generateAbi(const android::Vector<abi::Variant>& allVariants, size_t index); + static android::sp<Rule> generateDensity(const android::Vector<int>& allDensities, size_t index); +}; + +} // namespace split + +#endif // H_ANDROID_SPLIT_RULE_GENERATOR diff --git a/tools/split-select/RuleGenerator_test.cpp b/tools/split-select/RuleGenerator_test.cpp new file mode 100644 index 0000000..470cadc --- /dev/null +++ b/tools/split-select/RuleGenerator_test.cpp @@ -0,0 +1,110 @@ +/* + * 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. + */ + +#include "RuleGenerator.h" + +#include "aapt/SdkConstants.h" +#include "TestRules.h" + +#include <gtest/gtest.h> +#include <utils/Vector.h> + +using namespace android; +using namespace split::test; + +namespace split { + +TEST(RuleGeneratorTest, testAbiRules) { + Vector<abi::Variant> abis; + const ssize_t armeabiIndex = abis.add(abi::Variant_armeabi); + const ssize_t armeabi_v7aIndex = abis.add(abi::Variant_armeabi_v7a); + const ssize_t x86Index = abis.add(abi::Variant_x86); + + EXPECT_RULES_EQ(RuleGenerator::generateAbi(abis, armeabiIndex), + ContainsAnyRule(Rule::NATIVE_PLATFORM, "armeabi") + ); + + EXPECT_RULES_EQ(RuleGenerator::generateAbi(abis, armeabi_v7aIndex), + ContainsAnyRule(Rule::NATIVE_PLATFORM, "armeabi-v7a", "arm64-v8a") + ); + + EXPECT_RULES_EQ(RuleGenerator::generateAbi(abis, x86Index), + ContainsAnyRule(Rule::NATIVE_PLATFORM, "x86", "x86_64") + ); +} + +TEST(RuleGeneratorTest, densityConstantsAreSane) { + EXPECT_LT(263, (int) ConfigDescription::DENSITY_XHIGH); + EXPECT_GT(262, (int) ConfigDescription::DENSITY_HIGH); + EXPECT_LT(363, (int) ConfigDescription::DENSITY_XXHIGH); + EXPECT_GT(362, (int) ConfigDescription::DENSITY_XHIGH); +} + +TEST(RuleGeneratorTest, testDensityRules) { + Vector<int> densities; + const ssize_t highIndex = densities.add(ConfigDescription::DENSITY_HIGH); + const ssize_t xhighIndex = densities.add(ConfigDescription::DENSITY_XHIGH); + const ssize_t xxhighIndex = densities.add(ConfigDescription::DENSITY_XXHIGH); + + EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, highIndex), + AndRule() + .add(LtRule(Rule::SCREEN_DENSITY, 263)) + ); + + EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, xhighIndex), + AndRule() + .add(GtRule(Rule::SCREEN_DENSITY, 262)) + .add(LtRule(Rule::SCREEN_DENSITY, 363)) + ); + + EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, xxhighIndex), + AndRule() + .add(GtRule(Rule::SCREEN_DENSITY, 362)) + ); +} + +TEST(RuleGeneratorTest, testDensityRulesWithAnyDpi) { + Vector<int> densities; + const ssize_t highIndex = densities.add(ConfigDescription::DENSITY_HIGH); + const ssize_t xhighIndex = densities.add(ConfigDescription::DENSITY_XHIGH); + const ssize_t xxhighIndex = densities.add(ConfigDescription::DENSITY_XXHIGH); + const ssize_t anyIndex = densities.add(ConfigDescription::DENSITY_ANY); + + EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, highIndex), + AndRule() + .add(LtRule(Rule::SDK_VERSION, SDK_LOLLIPOP)) + .add(LtRule(Rule::SCREEN_DENSITY, 263)) + ); + + EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, xhighIndex), + AndRule() + .add(LtRule(Rule::SDK_VERSION, SDK_LOLLIPOP)) + .add(GtRule(Rule::SCREEN_DENSITY, 262)) + .add(LtRule(Rule::SCREEN_DENSITY, 363)) + ); + + EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, xxhighIndex), + AndRule() + .add(LtRule(Rule::SDK_VERSION, SDK_LOLLIPOP)) + .add(GtRule(Rule::SCREEN_DENSITY, 362)) + ); + + // We expect AlwaysTrue because anydpi always has attached v21 to the configuration + // and the rest of the rule generation code generates the sdk version checks. + EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, anyIndex), AlwaysTrue()); +} + +} // namespace split diff --git a/tools/split-select/Rule_test.cpp b/tools/split-select/Rule_test.cpp new file mode 100644 index 0000000..c6cff0d --- /dev/null +++ b/tools/split-select/Rule_test.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Rule.h" + +#include "SplitDescription.h" +#include "TestRules.h" + +#include <algorithm> +#include <gtest/gtest.h> +#include <string> +#include <utils/String8.h> + +using namespace android; +using namespace split::test; + +namespace split { + +TEST(RuleTest, generatesValidJson) { + Rule rule(AndRule() + .add(EqRule(Rule::SDK_VERSION, 7)) + .add(OrRule() + .add(GtRule(Rule::SCREEN_DENSITY, 10)) + .add(LtRule(Rule::SCREEN_DENSITY, 5)) + ) + ); + + // Expected + std::string expected( + "{" + " \"op\": \"AND_SUBRULES\"," + " \"subrules\": [" + " {" + " \"op\": \"EQUALS\"," + " \"property\": \"SDK_VERSION\"," + " \"args\": [7]" + " }," + " {" + " \"op\": \"OR_SUBRULES\"," + " \"subrules\": [" + " {" + " \"op\": \"GREATER_THAN\"," + " \"property\": \"SCREEN_DENSITY\"," + " \"args\": [10]" + " }," + " {" + " \"op\": \"LESS_THAN\"," + " \"property\": \"SCREEN_DENSITY\"," + " \"args\": [5]" + " }" + " ]" + " }" + " ]" + "}"); + expected.erase(std::remove_if(expected.begin(), expected.end(), ::isspace), expected.end()); + + // Result + std::string result(rule.toJson().string()); + result.erase(std::remove_if(result.begin(), result.end(), ::isspace), result.end()); + + ASSERT_EQ(expected, result); +} + +TEST(RuleTest, simplifiesSingleSubruleRules) { + sp<Rule> rule = new Rule(AndRule() + .add(EqRule(Rule::SDK_VERSION, 7)) + ); + + EXPECT_RULES_EQ(Rule::simplify(rule), EqRule(Rule::SDK_VERSION, 7)); +} + +TEST(RuleTest, simplifiesNestedSameOpSubrules) { + sp<Rule> rule = new Rule(AndRule() + .add(AndRule() + .add(EqRule(Rule::SDK_VERSION, 7)) + ) + .add(EqRule(Rule::SDK_VERSION, 8)) + ); + + EXPECT_RULES_EQ(Rule::simplify(rule), + AndRule() + .add(EqRule(Rule::SDK_VERSION, 7)) + .add(EqRule(Rule::SDK_VERSION, 8)) + ); +} + +} // namespace split diff --git a/tools/split-select/SplitDescription.cpp b/tools/split-select/SplitDescription.cpp new file mode 100644 index 0000000..99bc23d --- /dev/null +++ b/tools/split-select/SplitDescription.cpp @@ -0,0 +1,175 @@ +/* + * 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. + */ + +#include "SplitDescription.h" + +#include "aapt/AaptConfig.h" +#include "aapt/AaptUtil.h" + +#include <utils/String8.h> +#include <utils/Vector.h> + +using namespace android; + +namespace split { + +SplitDescription::SplitDescription() +: abi(abi::Variant_none) { +} + +int SplitDescription::compare(const SplitDescription& rhs) const { + int cmp; + cmp = (int)abi - (int)rhs.abi; + if (cmp != 0) return cmp; + return config.compareLogical(rhs.config); +} + +bool SplitDescription::isBetterThan(const SplitDescription& o, const SplitDescription& target) const { + if (abi != abi::Variant_none || o.abi != abi::Variant_none) { + abi::Family family = abi::getFamily(abi); + abi::Family oFamily = abi::getFamily(o.abi); + if (family != oFamily) { + return family != abi::Family_none; + } + + if (int(target.abi) - int(abi) < int(target.abi) - int(o.abi)) { + return true; + } + } + return config.isBetterThan(o.config, &target.config); +} + +bool SplitDescription::match(const SplitDescription& o) const { + if (abi != abi::Variant_none) { + abi::Family family = abi::getFamily(abi); + abi::Family oFamily = abi::getFamily(o.abi); + if (family != oFamily) { + return false; + } + + if (int(abi) > int(o.abi)) { + return false; + } + } + return config.match(o.config); +} + +String8 SplitDescription::toString() const { + String8 extension; + if (abi != abi::Variant_none) { + if (extension.isEmpty()) { + extension.append(":"); + } else { + extension.append("-"); + } + extension.append(abi::toString(abi)); + } + String8 str(config.toString()); + str.append(extension); + return str; +} + +ssize_t parseAbi(const Vector<String8>& parts, const ssize_t index, + SplitDescription* outSplit) { + const ssize_t N = parts.size(); + abi::Variant abi = abi::Variant_none; + ssize_t endIndex = index; + if (parts[endIndex] == "arm64") { + endIndex++; + if (endIndex < N) { + if (parts[endIndex] == "v8a") { + endIndex++; + abi = abi::Variant_arm64_v8a; + } + } + } else if (parts[endIndex] == "armeabi") { + endIndex++; + abi = abi::Variant_armeabi; + if (endIndex < N) { + if (parts[endIndex] == "v7a") { + endIndex++; + abi = abi::Variant_armeabi_v7a; + } + } + } else if (parts[endIndex] == "x86") { + endIndex++; + abi = abi::Variant_x86; + } else if (parts[endIndex] == "x86_64") { + endIndex++; + abi = abi::Variant_x86_64; + } else if (parts[endIndex] == "mips") { + endIndex++; + abi = abi::Variant_mips; + } else if (parts[endIndex] == "mips64") { + endIndex++; + abi = abi::Variant_mips64; + } + + if (abi == abi::Variant_none && endIndex != index) { + return -1; + } + + if (outSplit != NULL) { + outSplit->abi = abi; + } + return endIndex; +} + +bool SplitDescription::parse(const String8& str, SplitDescription* outSplit) { + ssize_t index = str.find(":"); + + String8 configStr; + String8 extensionStr; + if (index >= 0) { + configStr.setTo(str.string(), index); + extensionStr.setTo(str.string() + index + 1); + } else { + configStr.setTo(str); + } + + SplitDescription split; + if (!AaptConfig::parse(configStr, &split.config)) { + return false; + } + + Vector<String8> parts = AaptUtil::splitAndLowerCase(extensionStr, '-'); + const ssize_t N = parts.size(); + index = 0; + + if (extensionStr.length() == 0) { + goto success; + } + + index = parseAbi(parts, index, &split); + if (index < 0) { + return false; + } else { + if (index == N) { + goto success; + } + } + + // Unrecognized + return false; + +success: + if (outSplit != NULL) { + *outSplit = split; + } + return true; +} + +} // namespace split diff --git a/tools/split-select/SplitDescription.h b/tools/split-select/SplitDescription.h new file mode 100644 index 0000000..b13c9ee --- /dev/null +++ b/tools/split-select/SplitDescription.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef H_ANDROID_SPLIT_SPLIT_DESCRIPTION +#define H_ANDROID_SPLIT_SPLIT_DESCRIPTION + +#include "aapt/ConfigDescription.h" +#include "Abi.h" + +#include <utils/String8.h> +#include <utils/Vector.h> + +namespace split { + +struct SplitDescription { + SplitDescription(); + + ConfigDescription config; + abi::Variant abi; + + int compare(const SplitDescription& rhs) const; + inline bool operator<(const SplitDescription& rhs) const; + inline bool operator==(const SplitDescription& rhs) const; + inline bool operator!=(const SplitDescription& rhs) const; + + bool match(const SplitDescription& o) const; + bool isBetterThan(const SplitDescription& o, const SplitDescription& target) const; + + android::String8 toString() const; + + static bool parse(const android::String8& str, SplitDescription* outSplit); +}; + +ssize_t parseAbi(const android::Vector<android::String8>& parts, const ssize_t index, + SplitDescription* outSplit); + +bool SplitDescription::operator<(const SplitDescription& rhs) const { + return compare(rhs) < 0; +} + +bool SplitDescription::operator==(const SplitDescription& rhs) const { + return compare(rhs) == 0; +} + +bool SplitDescription::operator!=(const SplitDescription& rhs) const { + return compare(rhs) != 0; +} + +} // namespace split + +#endif // H_ANDROID_SPLIT_SPLIT_DESCRIPTION diff --git a/tools/split-select/SplitSelector.cpp b/tools/split-select/SplitSelector.cpp new file mode 100644 index 0000000..567e057 --- /dev/null +++ b/tools/split-select/SplitSelector.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <utils/KeyedVector.h> +#include <utils/SortedVector.h> +#include <utils/Vector.h> + +#include "Grouper.h" +#include "Rule.h" +#include "RuleGenerator.h" +#include "SplitSelector.h" + +namespace split { + +using namespace android; + +SplitSelector::SplitSelector() { +} + +SplitSelector::SplitSelector(const Vector<SplitDescription>& splits) + : mGroups(groupByMutualExclusivity(splits)) { +} + +static void selectBestFromGroup(const SortedVector<SplitDescription>& splits, + const SplitDescription& target, Vector<SplitDescription>& splitsOut) { + SplitDescription bestSplit; + bool isSet = false; + const size_t splitCount = splits.size(); + for (size_t j = 0; j < splitCount; j++) { + const SplitDescription& thisSplit = splits[j]; + if (!thisSplit.match(target)) { + continue; + } + + if (!isSet || thisSplit.isBetterThan(bestSplit, target)) { + isSet = true; + bestSplit = thisSplit; + } + } + + if (isSet) { + splitsOut.add(bestSplit); + } +} + +Vector<SplitDescription> SplitSelector::getBestSplits(const SplitDescription& target) const { + Vector<SplitDescription> bestSplits; + const size_t groupCount = mGroups.size(); + for (size_t i = 0; i < groupCount; i++) { + selectBestFromGroup(mGroups[i], target, bestSplits); + } + return bestSplits; +} + +KeyedVector<SplitDescription, sp<Rule> > SplitSelector::getRules() const { + KeyedVector<SplitDescription, sp<Rule> > rules; + + const size_t groupCount = mGroups.size(); + for (size_t i = 0; i < groupCount; i++) { + const SortedVector<SplitDescription>& splits = mGroups[i]; + const size_t splitCount = splits.size(); + for (size_t j = 0; j < splitCount; j++) { + sp<Rule> rule = Rule::simplify(RuleGenerator::generate(splits, j)); + if (rule != NULL) { + rules.add(splits[j], rule); + } + } + } + return rules; +} + +} // namespace split diff --git a/tools/split-select/SplitSelector.h b/tools/split-select/SplitSelector.h new file mode 100644 index 0000000..193fda7 --- /dev/null +++ b/tools/split-select/SplitSelector.h @@ -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. + */ + +#ifndef H_ANDROID_SPLIT_SPLIT_SELECTOR +#define H_ANDROID_SPLIT_SPLIT_SELECTOR + +#include <utils/KeyedVector.h> +#include <utils/SortedVector.h> +#include <utils/Vector.h> + +#include "Rule.h" +#include "SplitDescription.h" + +namespace split { + +class SplitSelector { +public: + SplitSelector(); + SplitSelector(const android::Vector<SplitDescription>& splits); + + android::Vector<SplitDescription> getBestSplits(const SplitDescription& target) const; + + android::KeyedVector<SplitDescription, android::sp<Rule> > getRules() const; + +private: + android::Vector<android::SortedVector<SplitDescription> > mGroups; +}; + +} // namespace split + +#endif // H_ANDROID_SPLIT_SPLIT_SELECTOR diff --git a/tools/split-select/SplitSelector_test.cpp b/tools/split-select/SplitSelector_test.cpp new file mode 100644 index 0000000..cbcd62c --- /dev/null +++ b/tools/split-select/SplitSelector_test.cpp @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#include <gtest/gtest.h> +#include <utils/String8.h> +#include <utils/Vector.h> + +#include "SplitDescription.h" +#include "SplitSelector.h" +#include "TestRules.h" + +namespace split { + +using namespace android; + +static ::testing::AssertionResult addSplit(Vector<SplitDescription>& splits, const char* str) { + SplitDescription split; + if (!SplitDescription::parse(String8(str), &split)) { + return ::testing::AssertionFailure() << str << " is not a valid configuration."; + } + splits.add(split); + return ::testing::AssertionSuccess(); +} + +TEST(SplitSelectorTest, rulesShouldMatchSelection) { + Vector<SplitDescription> splits; + ASSERT_TRUE(addSplit(splits, "hdpi")); + ASSERT_TRUE(addSplit(splits, "xhdpi")); + ASSERT_TRUE(addSplit(splits, "xxhdpi")); + ASSERT_TRUE(addSplit(splits, "mdpi")); + + SplitDescription targetSplit; + ASSERT_TRUE(SplitDescription::parse(String8("hdpi"), &targetSplit)); + + SplitSelector selector(splits); + SortedVector<SplitDescription> bestSplits; + bestSplits.merge(selector.getBestSplits(targetSplit)); + + SplitDescription expected; + ASSERT_TRUE(SplitDescription::parse(String8("hdpi"), &expected)); + EXPECT_GE(bestSplits.indexOf(expected), 0); + + KeyedVector<SplitDescription, sp<Rule> > rules = selector.getRules(); + ssize_t idx = rules.indexOfKey(expected); + ASSERT_GE(idx, 0); + sp<Rule> rule = rules[idx]; + ASSERT_TRUE(rule != NULL); + + ASSERT_GT(ResTable_config::DENSITY_HIGH, 180); + ASSERT_LT(ResTable_config::DENSITY_HIGH, 263); + + Rule expectedRule(test::AndRule() + .add(test::GtRule(Rule::SDK_VERSION, 3)) + .add(test::GtRule(Rule::SCREEN_DENSITY, 180)) + .add(test::LtRule(Rule::SCREEN_DENSITY, 263))); + EXPECT_RULES_EQ(rule, expectedRule); +} + +} // namespace split diff --git a/tools/split-select/TestRules.cpp b/tools/split-select/TestRules.cpp new file mode 100644 index 0000000..86ccd6a --- /dev/null +++ b/tools/split-select/TestRules.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TestRules.h" + +#include <utils/String8.h> + +using android::String8; +using android::sp; + +namespace split { +namespace test { + +const Rule EqRule(Rule::Key key, long value) { + Rule rule; + rule.op = Rule::EQUALS; + rule.key = key; + rule.longArgs.add(value); + return rule; +} + +const Rule GtRule(Rule::Key key, long value) { + Rule rule; + rule.op = Rule::GREATER_THAN; + rule.key = key; + rule.longArgs.add(value); + return rule; +} + +const Rule LtRule(Rule::Key key, long value) { + Rule rule; + rule.op = Rule::LESS_THAN; + rule.key = key; + rule.longArgs.add(value); + return rule; +} + +const Rule ContainsAnyRule(Rule::Key key, const char* str1) { + Rule rule; + rule.op = Rule::CONTAINS_ANY; + rule.key = key; + rule.stringArgs.add(String8(str1)); + return rule; +} + +const Rule ContainsAnyRule(Rule::Key key, const char* str1, const char* str2) { + Rule rule; + rule.op = Rule::CONTAINS_ANY; + rule.key = key; + rule.stringArgs.add(String8(str1)); + rule.stringArgs.add(String8(str2)); + return rule; +} + +const Rule AlwaysTrue() { + Rule rule; + rule.op = Rule::ALWAYS_TRUE; + return rule; +} + +::testing::AssertionResult RulePredFormat( + const char*, const char*, + const sp<Rule>& actual, const Rule& expected) { + const String8 expectedStr(expected.toJson()); + const String8 actualStr(actual != NULL ? actual->toJson() : String8()); + + if (expectedStr != actualStr) { + return ::testing::AssertionFailure() + << "Expected: " << expectedStr.string() << "\n" + << " Actual: " << actualStr.string(); + } + return ::testing::AssertionSuccess(); +} + + +} // namespace test +} // namespace split diff --git a/tools/split-select/TestRules.h b/tools/split-select/TestRules.h new file mode 100644 index 0000000..50b7ad1 --- /dev/null +++ b/tools/split-select/TestRules.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef H_AAPT_SPLIT_TEST_RULES +#define H_AAPT_SPLIT_TEST_RULES + +#include "Rule.h" + +#include <gtest/gtest.h> + +namespace split { +namespace test { + +struct AndRule : public Rule { + AndRule() { + op = Rule::AND_SUBRULES; + } + + AndRule& add(const Rule& rhs) { + subrules.add(new Rule(rhs)); + return *this; + } +}; + +struct OrRule : public Rule { + OrRule() { + op = Rule::OR_SUBRULES; + } + + OrRule& add(const Rule& rhs) { + subrules.add(new Rule(rhs)); + return *this; + } +}; + +const Rule EqRule(Rule::Key key, long value); +const Rule LtRule(Rule::Key key, long value); +const Rule GtRule(Rule::Key key, long value); +const Rule ContainsAnyRule(Rule::Key key, const char* str1); +const Rule ContainsAnyRule(Rule::Key key, const char* str1, const char* str2); +const Rule AlwaysTrue(); + +::testing::AssertionResult RulePredFormat( + const char* actualExpr, const char* expectedExpr, + const android::sp<Rule>& actual, const Rule& expected); + +#define EXPECT_RULES_EQ(actual, expected) \ + EXPECT_PRED_FORMAT2(::split::test::RulePredFormat, actual, expected) + +} // namespace test +} // namespace split + +#endif // H_AAPT_SPLIT_TEST_RULES |