am aee7225e: Merge "Process base APK" into lmp-mr1-dev

* commit 'aee7225e34376a90e8c25e5df53604085cc1b712':
  Process base APK
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 @@
     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 @@
     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 @@
             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 @@
     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 @@
 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 @@
             "  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 @@
             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 @@
     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 @@
     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 @@
     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 @@
             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 @@
         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 @@
         removeRuntimeQualifiers(&targetSplit.config);
     }
 
+    splitApkPaths.add(baseApkPath);
+
     KeyedVector<String8, Vector<SplitDescription> > apkPathSplitMap;
     KeyedVector<SplitDescription, String8> splitApkPathMap;
     Vector<SplitDescription> splitConfigs;
@@ -279,7 +336,7 @@
     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 @@
 
         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