diff options
author | Adam Lesinski <adamlesinski@google.com> | 2015-01-15 17:01:39 -0800 |
---|---|---|
committer | Adam Lesinski <adamlesinski@google.com> | 2015-01-16 14:11:30 -0800 |
commit | 42eea270a0a2bc54f454312817c41ac357e3a884 (patch) | |
tree | 36a4b3f3f658c40be17f3ce7c4bc3b6836bc817a | |
parent | 8d47bc97e642cd0d0caf31d09efe05d8dc233f27 (diff) | |
download | frameworks_base-42eea270a0a2bc54f454312817c41ac357e3a884.zip frameworks_base-42eea270a0a2bc54f454312817c41ac357e3a884.tar.gz frameworks_base-42eea270a0a2bc54f454312817c41ac357e3a884.tar.bz2 |
Process base APK
The base APK may have resources with configurations that compete
against some splits. The base APK must be involved in the selection
of splits.
Bug:18982001
Change-Id: Ieb29b5a36cf2c68e7831484d98a9fd275acd97e8
-rw-r--r-- | include/androidfw/ResourceTypes.h | 2 | ||||
-rw-r--r-- | libs/androidfw/ResourceTypes.cpp | 8 | ||||
-rw-r--r-- | tools/split-select/Android.mk | 4 | ||||
-rw-r--r-- | tools/split-select/Main.cpp | 233 | ||||
-rw-r--r-- | tools/split-select/SplitSelector.cpp | 85 | ||||
-rw-r--r-- | tools/split-select/SplitSelector.h | 44 | ||||
-rw-r--r-- | tools/split-select/SplitSelector_test.cpp | 72 |
7 files changed, 358 insertions, 90 deletions
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h index 5a28be5..f2d85b4 100644 --- a/include/androidfw/ResourceTypes.h +++ b/include/androidfw/ResourceTypes.h @@ -1779,7 +1779,7 @@ public: const DynamicRefTable* getDynamicRefTableForCookie(int32_t cookie) const; // Return the configurations (ResTable_config) that we know about - void getConfigurations(Vector<ResTable_config>* configs) const; + void getConfigurations(Vector<ResTable_config>* configs, bool ignoreMipmap=false) const; void getLocales(Vector<String8>* locales) const; diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index d7b9765..bdb53c3 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -5338,7 +5338,7 @@ const DynamicRefTable* ResTable::getDynamicRefTableForCookie(int32_t cookie) con return NULL; } -void ResTable::getConfigurations(Vector<ResTable_config>* configs) const +void ResTable::getConfigurations(Vector<ResTable_config>* configs, bool ignoreMipmap) const { const size_t packageCount = mPackageGroups.size(); for (size_t i = 0; i < packageCount; i++) { @@ -5349,6 +5349,12 @@ void ResTable::getConfigurations(Vector<ResTable_config>* configs) const const size_t numTypes = typeList.size(); for (size_t k = 0; k < numTypes; k++) { const Type* type = typeList[k]; + const ResStringPool& typeStrings = type->package->typeStrings; + if (ignoreMipmap && typeStrings.string8ObjectAt( + type->typeSpec->id - 1) == "mipmap") { + continue; + } + const size_t numConfigs = type->configs.size(); for (size_t m = 0; m < numConfigs; m++) { const ResTable_type* config = type->configs[m]; diff --git a/tools/split-select/Android.mk b/tools/split-select/Android.mk index 968d22b..013e570 100644 --- a/tools/split-select/Android.mk +++ b/tools/split-select/Android.mk @@ -29,12 +29,14 @@ sources := \ Grouper.cpp \ Rule.cpp \ RuleGenerator.cpp \ - SplitDescription.cpp + SplitDescription.cpp \ + SplitSelector.cpp testSources := \ Grouper_test.cpp \ Rule_test.cpp \ RuleGenerator_test.cpp \ + SplitSelector_test.cpp \ TestRules.cpp cIncludes := \ diff --git a/tools/split-select/Main.cpp b/tools/split-select/Main.cpp index 434494e..d3eb012 100644 --- a/tools/split-select/Main.cpp +++ b/tools/split-select/Main.cpp @@ -23,6 +23,7 @@ #include "Rule.h" #include "RuleGenerator.h" #include "SplitDescription.h" +#include "SplitSelector.h" #include <androidfw/AssetManager.h> #include <androidfw/ResourceTypes.h> @@ -36,12 +37,13 @@ namespace split { static void usage() { fprintf(stderr, "split-select --help\n" - "split-select --target <config> --split <path/to/apk> [--split <path/to/apk> [...]]\n" - "split-select --generate --split <path/to/apk> [--split <path/to/apk> [...]]\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" @@ -61,92 +63,33 @@ static void help() { " via JSON.\n"); } -class SplitSelector { -public: - SplitSelector(); - SplitSelector(const Vector<SplitDescription>& splits); - - Vector<SplitDescription> getBestSplits(const SplitDescription& target) const; - - template <typename RuleGenerator> - KeyedVector<SplitDescription, sp<Rule> > getRules() const; - -private: - Vector<SortedVector<SplitDescription> > mGroups; -}; - -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; -} - -template <typename RuleGenerator> -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; -} - 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) { +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<RuleGenerator>()); + 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]; @@ -155,12 +98,11 @@ void generate(const KeyedVector<String8, Vector<SplitDescription> >& splits) { masterRule->subrules.add(rules.valueFor(splitDescriptions[j])); } masterRule = Rule::simplify(masterRule); - fprintf(stdout, " {\n \"path\": \"%s\",\n \"rules\": %s\n }%s\n", + fprintf(stdout, " {\n \"path\": \"%s\",\n \"rules\": %s\n }", splits.keyAt(i).string(), - masterRule->toJson(2).string(), - i < apkSplitCount - 1 ? "," : ""); + masterRule->toJson(2).string()); } - fprintf(stdout, "]\n"); + fprintf(stdout, "\n]\n"); } static void removeRuntimeQualifiers(ConfigDescription* outConfig) { @@ -171,6 +113,95 @@ static void removeRuntimeQualifiers(ConfigDescription* outConfig) { 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; @@ -182,7 +213,7 @@ static Vector<SplitDescription> extractSplitDescriptionsFromApk(const String8& p const ResTable& res = assetManager.getResources(false); if (res.getError() == NO_ERROR) { Vector<ResTable_config> configs; - res.getConfigurations(&configs); + res.getConfigurations(&configs, true); const size_t configCount = configs.size(); for (size_t i = 0; i < configCount; i++) { splits.add(); @@ -214,13 +245,14 @@ static int main(int argc, char** 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, "Missing parameter for --split.\n"); + fprintf(stderr, "error: missing parameter for --target.\n"); usage(); return 1; } @@ -229,18 +261,33 @@ static int main(int argc, char** argv) { argc--; argv++; if (argc < 1) { - fprintf(stderr, "Missing parameter for --split.\n"); + 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, "Unknown argument '%s'\n", arg.string()); + fprintf(stderr, "error: unknown argument '%s'.\n", arg.string()); usage(); return 1; } @@ -253,15 +300,23 @@ static int main(int argc, char** argv) { return 1; } - if (splitApkPaths.size() == 0) { + 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, "Invalid --target config: '%s'\n", + fprintf(stderr, "error: invalid --target config: '%s'.\n", targetConfigStr.string()); usage(); return 1; @@ -272,6 +327,8 @@ static int main(int argc, char** argv) { removeRuntimeQualifiers(&targetSplit.config); } + splitApkPaths.add(baseApkPath); + KeyedVector<String8, Vector<SplitDescription> > apkPathSplitMap; KeyedVector<SplitDescription, String8> splitApkPathMap; Vector<SplitDescription> splitConfigs; @@ -279,7 +336,7 @@ static int main(int argc, char** argv) { for (size_t i = 0; i < splitCount; i++) { Vector<SplitDescription> splits = extractSplitDescriptionsFromApk(splitApkPaths[i]); if (splits.isEmpty()) { - fprintf(stderr, "Invalid --split path: '%s'. No splits found.\n", + fprintf(stderr, "error: invalid --split path: '%s'. No splits found.\n", splitApkPaths[i].string()); usage(); return 1; @@ -302,10 +359,12 @@ static int main(int argc, char** argv) { const size_t matchingSplitApkPathCount = matchingSplitPaths.size(); for (size_t i = 0; i < matchingSplitApkPathCount; i++) { - fprintf(stderr, "%s\n", matchingSplitPaths[i].string()); + if (matchingSplitPaths[i] != baseApkPath) { + fprintf(stdout, "%s\n", matchingSplitPaths[i].string()); + } } } else { - generate(apkPathSplitMap); + generate(apkPathSplitMap, baseApkPath); } return 0; } 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 |