Introduce anydpi density resource qualifier

This is meant to be used with scaleable vector
drawables, and are chosen as the best match unless
there is a configuration that matches the density
requested exactly.

Bug:17007265
Change-Id: Ic3288d0236fe0bff20bb1599aba2582c25b0db32
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index d19418b..e63fd07 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -533,6 +533,18 @@
     public static final int DENSITY_DPI_UNDEFINED = 0;
 
     /**
+     * Value for {@link #densityDpi} for resources that scale to any density (vector drawables).
+     * {@hide}
+     */
+    public static final int DENSITY_DPI_ANY = 0xfffe;
+
+    /**
+     * Value for {@link #densityDpi} for resources that are not meant to be scaled.
+     * {@hide}
+     */
+    public static final int DENSITY_DPI_NONE = 0xffff;
+
+    /**
      * The target screen density being rendered to,
      * corresponding to
      * <a href="{@docRoot}guide/topics/resources/providing-resources.html#DensityQualifier">density</a>
@@ -1453,7 +1465,7 @@
         }
 
         switch (config.densityDpi) {
-            case 0:
+            case DENSITY_DPI_UNDEFINED:
                 break;
             case 120:
                 parts.add("ldpi");
@@ -1476,6 +1488,11 @@
             case 640:
                 parts.add("xxxhdpi");
                 break;
+            case DENSITY_DPI_ANY:
+                parts.add("anydpi");
+                break;
+            case DENSITY_DPI_NONE:
+                parts.add("nodpi");
             default:
                 parts.add(config.densityDpi + "dpi");
                 break;
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index 1af497c..11568d2 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -954,6 +954,7 @@
         DENSITY_XHIGH = ACONFIGURATION_DENSITY_XHIGH,
         DENSITY_XXHIGH = ACONFIGURATION_DENSITY_XXHIGH,
         DENSITY_XXXHIGH = ACONFIGURATION_DENSITY_XXXHIGH,
+        DENSITY_ANY = ACONFIGURATION_DENSITY_ANY,
         DENSITY_NONE = ACONFIGURATION_DENSITY_NONE
     };
     
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 239d682..3f014ef 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -2206,13 +2206,30 @@
 
         if (screenType || o.screenType) {
             if (density != o.density) {
-                // density is tough.  Any density is potentially useful
+                // Use the system default density (DENSITY_MEDIUM, 160dpi) if none specified.
+                const int thisDensity = density ? density : int(ResTable_config::DENSITY_MEDIUM);
+                const int otherDensity = o.density ? o.density : int(ResTable_config::DENSITY_MEDIUM);
+
+                // We always prefer DENSITY_ANY over scaling a density bucket.
+                if (thisDensity == ResTable_config::DENSITY_ANY) {
+                    return true;
+                } else if (otherDensity == ResTable_config::DENSITY_ANY) {
+                    return false;
+                }
+
+                int requestedDensity = requested->density;
+                if (requested->density == 0 ||
+                        requested->density == ResTable_config::DENSITY_ANY) {
+                    requestedDensity = ResTable_config::DENSITY_MEDIUM;
+                }
+
+                // DENSITY_ANY is now dealt with. We should look to
+                // pick a density bucket and potentially scale it.
+                // Any density is potentially useful
                 // because the system will scale it.  Scaling down
                 // is generally better than scaling up.
-                // Default density counts as 160dpi (the system default)
-                // TODO - remove 160 constants
-                int h = (density?density:160);
-                int l = (o.density?o.density:160);
+                int h = thisDensity;
+                int l = otherDensity;
                 bool bImBigger = true;
                 if (l > h) {
                     int t = h;
@@ -2221,17 +2238,16 @@
                     bImBigger = false;
                 }
 
-                int reqValue = (requested->density?requested->density:160);
-                if (reqValue >= h) {
+                if (requestedDensity >= h) {
                     // requested value higher than both l and h, give h
                     return bImBigger;
                 }
-                if (l >= reqValue) {
+                if (l >= requestedDensity) {
                     // requested value lower than both l and h, give l
                     return !bImBigger;
                 }
                 // saying that scaling down is 2x better than up
-                if (((2 * l) - reqValue) * h > reqValue * reqValue) {
+                if (((2 * l) - requestedDensity) * h > requestedDensity * requestedDensity) {
                     return !bImBigger;
                 } else {
                     return bImBigger;
@@ -2702,6 +2718,9 @@
             case ResTable_config::DENSITY_NONE:
                 res.append("nodpi");
                 break;
+            case ResTable_config::DENSITY_ANY:
+                res.append("anydpi");
+                break;
             default:
                 res.appendFormat("%ddpi", dtohs(density));
                 break;
diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk
index 4ff6eec..a10c387 100644
--- a/libs/androidfw/tests/Android.mk
+++ b/libs/androidfw/tests/Android.mk
@@ -21,8 +21,9 @@
 LOCAL_PATH:= $(call my-dir)
 testFiles := \
     ByteBucketArray_test.cpp \
+    Config_test.cpp \
+    ConfigLocale_test.cpp \
     Idmap_test.cpp \
-    ResourceTypes_test.cpp \
     ResTable_test.cpp \
     Split_test.cpp \
     TypeWrappers_test.cpp \
diff --git a/libs/androidfw/tests/ResourceTypes_test.cpp b/libs/androidfw/tests/ConfigLocale_test.cpp
similarity index 90%
rename from libs/androidfw/tests/ResourceTypes_test.cpp
rename to libs/androidfw/tests/ConfigLocale_test.cpp
index f00a2d9..4999594 100644
--- a/libs/androidfw/tests/ResourceTypes_test.cpp
+++ b/libs/androidfw/tests/ConfigLocale_test.cpp
@@ -21,7 +21,7 @@
 #include <gtest/gtest.h>
 namespace android {
 
-TEST(ResourceTypesTest, ResourceConfig_packAndUnpack2LetterLanguage) {
+TEST(ConfigLocaleTest, packAndUnpack2LetterLanguage) {
      ResTable_config config;
      config.packLanguage("en");
 
@@ -44,7 +44,7 @@
      EXPECT_EQ(0, out[3]);
 }
 
-TEST(ResourceTypesTest, ResourceConfig_packAndUnpack2LetterRegion) {
+TEST(ConfigLocaleTest, packAndUnpack2LetterRegion) {
      ResTable_config config;
      config.packRegion("US");
 
@@ -59,7 +59,7 @@
      EXPECT_EQ(0, out[3]);
 }
 
-TEST(ResourceTypesTest, ResourceConfig_packAndUnpack3LetterLanguage) {
+TEST(ConfigLocaleTest, packAndUnpack3LetterLanguage) {
      ResTable_config config;
      config.packLanguage("eng");
 
@@ -75,7 +75,7 @@
      EXPECT_EQ(0, out[3]);
 }
 
-TEST(ResourceTypesTest, ResourceConfig_packAndUnpack3LetterLanguageAtOffset16) {
+TEST(ConfigLocaleTest, packAndUnpack3LetterLanguageAtOffset16) {
      ResTable_config config;
      config.packLanguage("tgp");
 
@@ -88,8 +88,8 @@
      // which is equivalent to:
      // 1  [0]   [1]   [2]
      // 1-01111-00110-10011
-     EXPECT_EQ(0xbc, config.language[0]);
-     EXPECT_EQ(0xd3, config.language[1]);
+     EXPECT_EQ(char(0xbc), config.language[0]);
+     EXPECT_EQ(char(0xd3), config.language[1]);
 
      char out[4] = { 1, 1, 1, 1};
      config.unpackLanguage(out);
@@ -99,7 +99,7 @@
      EXPECT_EQ(0, out[3]);
 }
 
-TEST(ResourceTypesTest, ResourceConfig_packAndUnpack3LetterRegion) {
+TEST(ConfigLocaleTest, packAndUnpack3LetterRegion) {
      ResTable_config config;
      config.packRegion("419");
 
@@ -131,7 +131,7 @@
      }
 }
 
-TEST(ResourceTypesTest, IsMoreSpecificThan) {
+TEST(ConfigLocaleTest, IsMoreSpecificThan) {
     ResTable_config l;
     ResTable_config r;
 
@@ -170,7 +170,7 @@
     EXPECT_TRUE(r.isMoreSpecificThan(l));
 }
 
-TEST(ResourceTypesTest, setLocale) {
+TEST(ConfigLocaleTest, setLocale) {
     ResTable_config test;
     test.setBcp47Locale("en-US");
     EXPECT_EQ('e', test.language[0]);
diff --git a/libs/androidfw/tests/Config_test.cpp b/libs/androidfw/tests/Config_test.cpp
new file mode 100644
index 0000000..ef30df4
--- /dev/null
+++ b/libs/androidfw/tests/Config_test.cpp
@@ -0,0 +1,103 @@
+/*
+ * 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 <androidfw/ResourceTypes.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include "TestHelpers.h"
+#include <gtest/gtest.h>
+
+namespace android {
+
+static ResTable_config selectBest(const ResTable_config& target,
+        const Vector<ResTable_config>& configs) {
+    ResTable_config bestConfig;
+    memset(&bestConfig, 0, sizeof(bestConfig));
+    const size_t configCount = configs.size();
+    for (size_t i = 0; i < configCount; i++) {
+        const ResTable_config& thisConfig = configs[i];
+        if (!thisConfig.match(target)) {
+            continue;
+        }
+
+        if (thisConfig.isBetterThan(bestConfig, &target)) {
+            bestConfig = thisConfig;
+        }
+    }
+    return bestConfig;
+}
+
+static ResTable_config buildDensityConfig(int density) {
+    ResTable_config config;
+    memset(&config, 0, sizeof(config));
+    config.density = uint16_t(density);
+    config.sdkVersion = 4;
+    return config;
+}
+
+TEST(ConfigTest, shouldSelectBestDensity) {
+    ResTable_config deviceConfig;
+    memset(&deviceConfig, 0, sizeof(deviceConfig));
+    deviceConfig.density = ResTable_config::DENSITY_XHIGH;
+    deviceConfig.sdkVersion = 21;
+
+    Vector<ResTable_config> configs;
+
+    ResTable_config expectedBest = buildDensityConfig(ResTable_config::DENSITY_HIGH);
+    configs.add(expectedBest);
+    ASSERT_EQ(expectedBest, selectBest(deviceConfig, configs));
+
+    expectedBest = buildDensityConfig(ResTable_config::DENSITY_XXHIGH);
+    configs.add(expectedBest);
+    ASSERT_EQ(expectedBest, selectBest(deviceConfig, configs));
+
+    expectedBest = buildDensityConfig(int(ResTable_config::DENSITY_XXHIGH) - 20);
+    configs.add(expectedBest);
+    ASSERT_EQ(expectedBest, selectBest(deviceConfig, configs));
+
+    configs.add(buildDensityConfig(int(ResTable_config::DENSITY_HIGH) + 20));
+    ASSERT_EQ(expectedBest, selectBest(deviceConfig, configs));
+
+    expectedBest = buildDensityConfig(ResTable_config::DENSITY_XHIGH);
+    configs.add(expectedBest);
+    ASSERT_EQ(expectedBest, selectBest(deviceConfig, configs));
+
+    expectedBest = buildDensityConfig(ResTable_config::DENSITY_ANY);
+    expectedBest.sdkVersion = 21;
+    configs.add(expectedBest);
+    ASSERT_EQ(expectedBest, selectBest(deviceConfig, configs));
+}
+
+TEST(ConfigTest, shouldSelectBestDensityWhenNoneSpecified) {
+    ResTable_config deviceConfig;
+    memset(&deviceConfig, 0, sizeof(deviceConfig));
+    deviceConfig.sdkVersion = 21;
+
+    Vector<ResTable_config> configs;
+    configs.add(buildDensityConfig(ResTable_config::DENSITY_HIGH));
+
+    ResTable_config expectedBest = buildDensityConfig(ResTable_config::DENSITY_MEDIUM);
+    configs.add(expectedBest);
+    ASSERT_EQ(expectedBest, selectBest(deviceConfig, configs));
+
+    expectedBest = buildDensityConfig(ResTable_config::DENSITY_ANY);
+    configs.add(expectedBest);
+    ASSERT_EQ(expectedBest, selectBest(deviceConfig, configs));
+}
+
+}  // namespace android.
diff --git a/libs/androidfw/tests/TestHelpers.h b/libs/androidfw/tests/TestHelpers.h
index 75a233a..fe2e5ce 100644
--- a/libs/androidfw/tests/TestHelpers.h
+++ b/libs/androidfw/tests/TestHelpers.h
@@ -3,6 +3,7 @@
 
 #include <ostream>
 
+#include <androidfw/ResourceTypes.h>
 #include <utils/String8.h>
 #include <utils/String16.h>
 
@@ -14,4 +15,16 @@
     return out << android::String8(str).string();
 }
 
+namespace android {
+
+static inline bool operator==(const android::ResTable_config& a, const android::ResTable_config& b) {
+    return memcmp(&a, &b, sizeof(a)) == 0;
+}
+
+static inline ::std::ostream& operator<<(::std::ostream& out, const android::ResTable_config& c) {
+    return out << c.toString().string();
+}
+
+} // namespace android
+
 #endif // __TEST_HELPERS_H
diff --git a/tools/aapt/AaptConfig.cpp b/tools/aapt/AaptConfig.cpp
index 69a9c7f..32a0cd3 100644
--- a/tools/aapt/AaptConfig.cpp
+++ b/tools/aapt/AaptConfig.cpp
@@ -255,6 +255,8 @@
                 != ResTable_config::SCREENLONG_ANY
             || config->density != ResTable_config::DENSITY_DEFAULT) {
         minSdk = SDK_DONUT;
+    } else if ((config->density == ResTable_config::DENSITY_ANY)) {
+        minSdk = SDK_L;
     }
 
     if (minSdk > config->sdkVersion) {
@@ -477,6 +479,11 @@
         return true;
     }
 
+    if (strcmp(name, "anydpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_ANY;
+        return true;
+    }
+
     if (strcmp(name, "nodpi") == 0) {
         if (out) out->density = ResTable_config::DENSITY_NONE;
         return true;
diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h
index 1439f14..af49461 100644
--- a/tools/aapt/Bundle.h
+++ b/tools/aapt/Bundle.h
@@ -24,6 +24,7 @@
     SDK_HONEYCOMB_MR2 = 13,
     SDK_ICE_CREAM_SANDWICH = 14,
     SDK_ICE_CREAM_SANDWICH_MR1 = 15,
+    SDK_L = 21,
 };
 
 /*