summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Lesinski <adamlesinski@google.com>2015-01-15 17:01:39 -0800
committerAdam Lesinski <adamlesinski@google.com>2015-01-16 14:11:30 -0800
commit42eea270a0a2bc54f454312817c41ac357e3a884 (patch)
tree36a4b3f3f658c40be17f3ce7c4bc3b6836bc817a
parent8d47bc97e642cd0d0caf31d09efe05d8dc233f27 (diff)
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.h2
-rw-r--r--libs/androidfw/ResourceTypes.cpp8
-rw-r--r--tools/split-select/Android.mk4
-rw-r--r--tools/split-select/Main.cpp233
-rw-r--r--tools/split-select/SplitSelector.cpp85
-rw-r--r--tools/split-select/SplitSelector.h44
-rw-r--r--tools/split-select/SplitSelector_test.cpp72
7 files changed, 358 insertions, 90 deletions
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index 5a28be5c2581..f2d85b4c9388 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 d7b976553e93..bdb53c36d991 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 968d22ba39f3..013e570fc35e 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 434494ead3a8..d3eb012a80e9 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 000000000000..567e05752d45
--- /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 000000000000..193fda7077d3
--- /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 000000000000..cbcd62ce6fef
--- /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