First commit of split-select tool

This tool emits a set of rules as JSON for when a Split APK
should match a target device.

Change-Id: I8bfbdfbdb51efcfc645889dd03e1961f16e39645
diff --git a/tools/split-select/Abi.cpp b/tools/split-select/Abi.cpp
new file mode 100644
index 0000000..20654b6
--- /dev/null
+++ b/tools/split-select/Abi.cpp
@@ -0,0 +1,83 @@
+/*
+ * 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"
+
+namespace split {
+namespace abi {
+
+static const std::vector<Variant> sNoneVariants = {};
+static const std::vector<Variant> sArmVariants =
+        {Variant::armeabi, Variant::armeabi_v7a, Variant::arm64_v8a};
+static const std::vector<Variant> sIntelVariants = {Variant::x86, Variant::x86_64};
+static const std::vector<Variant> sMipsVariants = {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 std::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..3e00eba
--- /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 <vector>
+
+namespace split {
+namespace abi {
+
+enum class Variant {
+    none = 0,
+    armeabi,
+    armeabi_v7a,
+    arm64_v8a,
+    x86,
+    x86_64,
+    mips,
+    mips64,
+};
+
+enum class Family {
+    none,
+    arm,
+    intel,
+    mips,
+};
+
+Family getFamily(Variant variant);
+const std::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..d0b7287
--- /dev/null
+++ b/tools/split-select/Android.mk
@@ -0,0 +1,119 @@
+#
+# 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)),)
+
+# TODO(adamlesinski): Enable OS X builds when I figure out how
+# to build with clang and libc++
+ifneq ($(HOST_OS),darwin)
+
+# ==========================================================
+# 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
+
+testSources := \
+    Grouper_test.cpp \
+    Rule_test.cpp \
+    RuleGenerator_test.cpp
+
+cIncludes := \
+    external/zlib \
+    frameworks/base/tools
+
+hostLdLibs :=
+hostStaticLibs := \
+    libaapt \
+    libandroidfw \
+    libpng \
+    liblog \
+    libutils \
+    libcutils \
+    libexpat \
+    libziparchive-host
+
+cFlags := -std=c++11 -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 # Not OS X
+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..15edf89
--- /dev/null
+++ b/tools/split-select/Grouper.cpp
@@ -0,0 +1,83 @@
+/*
+ * 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 <utils/KeyedVector.h>
+#include <utils/Vector.h>
+
+using namespace android;
+
+namespace split {
+
+template <typename Key, typename Value>
+static void addToVector(KeyedVector<Key, SortedVector<Value> >& group,
+        const Key& key, const Value& value) {
+    ssize_t idx = group.indexOfKey(key);
+    if (idx < 0) {
+        idx = group.add(key, SortedVector<Value>());
+    }
+    group.editValueAt(idx).add(value);
+}
+
+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;
+    for (const SplitDescription& split : splits) {
+        if (split.config.density != 0) {
+            SplitDescription key(split);
+            key.config.density = 0;
+            key.config.sdkVersion = 0; // Ignore density so we can support anydpi.
+            addToVector(densityGroups, key, split);
+        } else if (split.abi != abi::Variant::none) {
+            SplitDescription key(split);
+            key.abi = abi::Variant::none;
+            addToVector(abiGroups, key, split);
+        } else if (split.config.locale != 0) {
+            SplitDescription key(split);
+            key.config.clearLocale();
+            addToVector(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..4d146cd
--- /dev/null
+++ b/tools/split-select/Grouper_test.cpp
@@ -0,0 +1,151 @@
+/*
+ * 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 <initializer_list>
+#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(std::initializer_list<const char*> l);
+
+    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(std::initializer_list<const char*> l) {
+    Vector<SplitDescription> splits;
+    for (const char* str : l) {
+        splits.add();
+        if (!SplitDescription::parse(String8(str), &splits.editTop())) {
+            ADD_FAILURE() << "Failed to parse SplitDescription " << str;
+            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..d6251c3
--- /dev/null
+++ b/tools/split-select/Main.cpp
@@ -0,0 +1,314 @@
+/*
+ * 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 <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> --split <path/to/apk> [--split <path/to/apk> [...]]\n"
+            "split-select --generate --split <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"
+            "  --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");
+}
+
+class SplitSelector {
+public:
+    SplitSelector() = default;
+    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(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) {
+    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>());
+
+    fprintf(stdout, "[\n");
+    for (size_t i = 0; i < apkSplitCount; i++) {
+        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  }%s\n",
+                splits.keyAt(i).string(),
+                masterRule->toJson(2).string(),
+                i < apkSplitCount - 1 ? "," : "");
+    }
+    fprintf(stdout, "]\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;
+}
+
+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);
+        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;
+    while (argc > 0) {
+        const String8 arg(*argv);
+        if (arg == "--target") {
+            argc--;
+            argv++;
+            if (argc < 1) {
+                fprintf(stderr, "Missing parameter for --split.\n");
+                usage();
+                return 1;
+            }
+            targetConfigStr.setTo(*argv);
+        } else if (arg == "--split") {
+            argc--;
+            argv++;
+            if (argc < 1) {
+                fprintf(stderr, "Missing parameter for --split.\n");
+                usage();
+                return 1;
+            }
+            splitApkPaths.add(String8(*argv));
+        } else if (arg == "--generate") {
+            generateFlag = true;
+        } else if (arg == "--help") {
+            help();
+            return 0;
+        } else {
+            fprintf(stderr, "Unknown argument '%s'\n", arg.string());
+            usage();
+            return 1;
+        }
+        argc--;
+        argv++;
+    }
+
+    if (!generateFlag && targetConfigStr == "") {
+        usage();
+        return 1;
+    }
+
+    if (splitApkPaths.size() == 0) {
+        usage();
+        return 1;
+    }
+
+    SplitDescription targetSplit;
+    if (!generateFlag) {
+        if (!SplitDescription::parse(targetConfigStr, &targetSplit)) {
+            fprintf(stderr, "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);
+    }
+
+    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, "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++) {
+            fprintf(stderr, "%s\n", matchingSplitPaths[i].string());
+        }
+    } else {
+        generate(apkPathSplitMap);
+    }
+    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..9559fe2
--- /dev/null
+++ b/tools/split-select/Rule.cpp
@@ -0,0 +1,196 @@
+/*
+ * 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--;
+    }
+}
+
+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..8029931
--- /dev/null
+++ b/tools/split-select/Rule.h
@@ -0,0 +1,78 @@
+/*
+ * 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();
+
+    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..669ae78
--- /dev/null
+++ b/tools/split-select/RuleGenerator.cpp
@@ -0,0 +1,153 @@
+/*
+ * 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 <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) {
+    sp<Rule> densityRule = new Rule();
+    densityRule->op = Rule::AND_SUBRULES;
+
+    const bool anyDensity = allDensities[index] == ResTable_config::DENSITY_ANY;
+    sp<Rule> any = new Rule();
+    any->op = Rule::EQUALS;
+    any->key = Rule::SCREEN_DENSITY;
+    any->longArgs.add((int)ResTable_config::DENSITY_ANY);
+    any->negate = !anyDensity;
+    densityRule->subrules.add(any);
+
+    if (!anyDensity) {
+        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;
+}
+
+sp<Rule> RuleGenerator::generateAbi(const Vector<abi::Variant>& splitAbis, size_t index) {
+    const abi::Variant thisAbi = splitAbis[index];
+    const std::vector<abi::Variant>& familyVariants = abi::getVariants(abi::getFamily(thisAbi));
+
+    std::vector<abi::Variant>::const_iterator start =
+            std::find(familyVariants.begin(), familyVariants.end(), thisAbi);
+
+    std::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..60baabe
--- /dev/null
+++ b/tools/split-select/RuleGenerator_test.cpp
@@ -0,0 +1,155 @@
+/*
+ * 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 <algorithm>
+#include <gtest/gtest.h>
+#include <utils/String8.h>
+
+using namespace android;
+
+namespace split {
+
+static void expectDensityRule(const Vector<int>& densities, int density, int greaterThan, int lessThan);
+static void expectAbiRule(const Vector<abi::Variant>& abis, abi::Variant variant,
+        std::initializer_list<const char*> matches);
+
+TEST(RuleGeneratorTest, testAbiRules) {
+    Vector<abi::Variant> abis;
+    abis.add(abi::Variant::armeabi);
+    abis.add(abi::Variant::armeabi_v7a);
+    abis.add(abi::Variant::x86);
+    std::sort(abis.begin(), abis.end());
+
+    expectAbiRule(abis, abi::Variant::armeabi, {"armeabi"});
+    expectAbiRule(abis, abi::Variant::armeabi_v7a, {"armeabi-v7a", "arm64-v8a"});
+    expectAbiRule(abis, abi::Variant::x86, {"x86", "x86_64"});
+}
+
+TEST(RuleGeneratorTest, testDensityRules) {
+    Vector<int> densities;
+    densities.add(ConfigDescription::DENSITY_HIGH);
+    densities.add(ConfigDescription::DENSITY_XHIGH);
+    densities.add(ConfigDescription::DENSITY_XXHIGH);
+    densities.add(ConfigDescription::DENSITY_ANY);
+
+    ASSERT_LT(263, ConfigDescription::DENSITY_XHIGH);
+    ASSERT_GT(262, ConfigDescription::DENSITY_HIGH);
+    ASSERT_LT(363, ConfigDescription::DENSITY_XXHIGH);
+    ASSERT_GT(362, ConfigDescription::DENSITY_XHIGH);
+
+    expectDensityRule(densities, ConfigDescription::DENSITY_HIGH, 0, 263);
+    expectDensityRule(densities, ConfigDescription::DENSITY_XHIGH, 262, 363);
+    expectDensityRule(densities, ConfigDescription::DENSITY_XXHIGH, 362, 0);
+    expectDensityRule(densities, ConfigDescription::DENSITY_ANY, 0, 0);
+}
+
+//
+// Helper methods.
+//
+
+static void expectDensityRule(const Vector<int>& densities, int density, int greaterThan, int lessThan) {
+    const int* iter = std::find(densities.begin(), densities.end(), density);
+    if (densities.end() == iter) {
+        ADD_FAILURE() << density << "dpi was not in the density list.";
+        return;
+    }
+
+    sp<Rule> rule = RuleGenerator::generateDensity(densities, iter - densities.begin());
+    if (rule->op != Rule::AND_SUBRULES) {
+        ADD_FAILURE() << "Op in rule for " << density << "dpi is not Rule::AND_SUBRULES.";
+        return;
+    }
+
+    size_t index = 0;
+
+    bool isAnyDpi = density == ConfigDescription::DENSITY_ANY;
+
+    sp<Rule> anyDpiRule = rule->subrules[index++];
+    EXPECT_EQ(Rule::EQUALS, anyDpiRule->op)
+            << "for " << density << "dpi ANY DPI rule";
+    EXPECT_EQ(Rule::SCREEN_DENSITY, anyDpiRule->key)
+            << "for " << density << "dpi ANY DPI rule";
+    EXPECT_EQ(isAnyDpi == false, anyDpiRule->negate)
+            << "for " << density << "dpi ANY DPI rule";
+    if (anyDpiRule->longArgs.size() == 1) {
+        EXPECT_EQ(ConfigDescription::DENSITY_ANY, anyDpiRule->longArgs[0])
+            << "for " << density << "dpi ANY DPI rule";
+    } else {
+        EXPECT_EQ(1u, anyDpiRule->longArgs.size())
+            << "for " << density << "dpi ANY DPI rule";
+    }
+
+
+    if (greaterThan != 0) {
+        sp<Rule> greaterThanRule = rule->subrules[index++];
+        EXPECT_EQ(Rule::GREATER_THAN, greaterThanRule->op)
+                << "for " << density << "dpi GREATER_THAN rule";
+        EXPECT_EQ(Rule::SCREEN_DENSITY, greaterThanRule->key)
+                << "for " << density << "dpi GREATER_THAN rule";
+        if (greaterThanRule->longArgs.size() == 1) {
+            EXPECT_EQ(greaterThan, greaterThanRule->longArgs[0])
+                << "for " << density << "dpi GREATER_THAN rule";
+        } else {
+            EXPECT_EQ(1u, greaterThanRule->longArgs.size())
+                << "for " << density << "dpi GREATER_THAN rule";
+        }
+    }
+
+    if (lessThan != 0) {
+        sp<Rule> lessThanRule = rule->subrules[index++];
+        EXPECT_EQ(Rule::LESS_THAN, lessThanRule->op)
+                << "for " << density << "dpi LESS_THAN rule";
+        EXPECT_EQ(Rule::SCREEN_DENSITY, lessThanRule->key)
+                << "for " << density << "dpi LESS_THAN rule";
+        if (lessThanRule->longArgs.size() == 1) {
+            EXPECT_EQ(lessThan, lessThanRule->longArgs[0])
+                << "for " << density << "dpi LESS_THAN rule";
+        } else {
+            EXPECT_EQ(1u, lessThanRule->longArgs.size())
+                << "for " << density << "dpi LESS_THAN rule";
+        }
+    }
+}
+
+static void expectAbiRule(const Vector<abi::Variant>& abis, abi::Variant variant,
+        std::initializer_list<const char*> matches) {
+    const abi::Variant* iter = std::find(abis.begin(), abis.end(), variant);
+    if (abis.end() == iter) {
+        ADD_FAILURE() << abi::toString(variant) << " was not in the abi list.";
+        return;
+    }
+
+    sp<Rule> rule = RuleGenerator::generateAbi(abis, iter - abis.begin());
+
+    EXPECT_EQ(Rule::CONTAINS_ANY, rule->op)
+            << "for " << abi::toString(variant) << " rule";
+    EXPECT_EQ(Rule::NATIVE_PLATFORM, rule->key)
+            << " for " << abi::toString(variant) << " rule";
+    EXPECT_EQ(matches.size(), rule->stringArgs.size())
+            << " for " << abi::toString(variant) << " rule";
+
+    for (const char* match : matches) {
+        if (rule->stringArgs.end() ==
+                std::find(rule->stringArgs.begin(), rule->stringArgs.end(), String8(match))) {
+            ADD_FAILURE() << "Rule for abi " << abi::toString(variant)
+                    << " does not contain match for expected abi " << match;
+        }
+    }
+}
+
+} // namespace split
diff --git a/tools/split-select/Rule_test.cpp b/tools/split-select/Rule_test.cpp
new file mode 100644
index 0000000..aca7433
--- /dev/null
+++ b/tools/split-select/Rule_test.cpp
@@ -0,0 +1,147 @@
+/*
+ * 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 <algorithm>
+#include <string>
+#include <gtest/gtest.h>
+#include <utils/String8.h>
+
+using namespace android;
+
+namespace split {
+
+TEST(RuleTest, generatesValidJson) {
+    sp<Rule> rule = new Rule();
+    rule->op = Rule::AND_SUBRULES;
+
+    sp<Rule> subrule = new Rule();
+    subrule->op = Rule::EQUALS;
+    subrule->key = Rule::SDK_VERSION;
+    subrule->longArgs.add(7);
+    rule->subrules.add(subrule);
+
+    subrule = new Rule();
+    subrule->op = Rule::OR_SUBRULES;
+    rule->subrules.add(subrule);
+
+    sp<Rule> subsubrule = new Rule();
+    subsubrule->op = Rule::GREATER_THAN;
+    subsubrule->key = Rule::SCREEN_DENSITY;
+    subsubrule->longArgs.add(10);
+    subrule->subrules.add(subsubrule);
+
+    subsubrule = new Rule();
+    subsubrule->op = Rule::LESS_THAN;
+    subsubrule->key = Rule::SCREEN_DENSITY;
+    subsubrule->longArgs.add(5);
+    subrule->subrules.add(subsubrule);
+
+    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]"
+            "        }"
+            "      ]"
+            "     }"
+            "  ]"
+            "}");
+    // Trim
+    expected.erase(std::remove_if(expected.begin(), expected.end(), ::isspace), expected.end());
+
+    std::string result(rule->toJson().string());
+
+    // Trim
+    result.erase(std::remove_if(result.begin(), result.end(), ::isspace), result.end());
+
+    ASSERT_EQ(expected, result);
+}
+
+TEST(RuleTest, simplifiesSingleSubruleRules) {
+    sp<Rule> rule = new Rule();
+    rule->op = Rule::AND_SUBRULES;
+
+    sp<Rule> subrule = new Rule();
+    subrule->op = Rule::EQUALS;
+    subrule->key = Rule::SDK_VERSION;
+    subrule->longArgs.add(7);
+    rule->subrules.add(subrule);
+
+    sp<Rule> simplified = Rule::simplify(rule);
+    EXPECT_EQ(Rule::EQUALS, simplified->op);
+    EXPECT_EQ(Rule::SDK_VERSION, simplified->key);
+    ASSERT_EQ(1u, simplified->longArgs.size());
+    EXPECT_EQ(7, simplified->longArgs[0]);
+}
+
+TEST(RuleTest, simplifiesNestedSameOpSubrules) {
+    sp<Rule> rule = new Rule();
+    rule->op = Rule::AND_SUBRULES;
+
+    sp<Rule> subrule = new Rule();
+    subrule->op = Rule::AND_SUBRULES;
+    rule->subrules.add(subrule);
+
+    sp<Rule> subsubrule = new Rule();
+    subsubrule->op = Rule::EQUALS;
+    subsubrule->key = Rule::SDK_VERSION;
+    subsubrule->longArgs.add(7);
+    subrule->subrules.add(subsubrule);
+
+    subrule = new Rule();
+    subrule->op = Rule::EQUALS;
+    subrule->key = Rule::SDK_VERSION;
+    subrule->longArgs.add(8);
+    rule->subrules.add(subrule);
+
+    sp<Rule> simplified = Rule::simplify(rule);
+    EXPECT_EQ(Rule::AND_SUBRULES, simplified->op);
+    ASSERT_EQ(2u, simplified->subrules.size());
+
+    sp<Rule> simplifiedSubrule = simplified->subrules[0];
+    EXPECT_EQ(Rule::EQUALS, simplifiedSubrule->op);
+    EXPECT_EQ(Rule::SDK_VERSION, simplifiedSubrule->key);
+    ASSERT_EQ(1u, simplifiedSubrule->longArgs.size());
+    EXPECT_EQ(7, simplifiedSubrule->longArgs[0]);
+
+    simplifiedSubrule = simplified->subrules[1];
+    EXPECT_EQ(Rule::EQUALS, simplifiedSubrule->op);
+    EXPECT_EQ(Rule::SDK_VERSION, simplifiedSubrule->key);
+    ASSERT_EQ(1u, simplifiedSubrule->longArgs.size());
+    EXPECT_EQ(8, simplifiedSubrule->longArgs[0]);
+}
+
+} // namespace split
diff --git a/tools/split-select/SplitDescription.cpp b/tools/split-select/SplitDescription.cpp
new file mode 100644
index 0000000..8037ef0
--- /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..5fcafc8
--- /dev/null
+++ b/tools/split-select/SplitDescription.h
@@ -0,0 +1,65 @@
+/*
+ * 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();
+    SplitDescription(const SplitDescription&) = default;
+
+    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