Add -round and -notround qualifier to android runtime/aapt

The round qualifier denotes a device with a screen shape that
is round. The qualifier shows up after the 'long/notlong' qualifier
and before the orientation 'port/land/square' qualifiers.

Change-Id: I3044258b2703a9165694b79725bade770fa6cea1
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 74a9e4e..dca04f5 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -615,6 +615,10 @@
 
     const char* locale8 = locale != NULL ? env->GetStringUTFChars(locale, NULL) : NULL;
 
+    // Constants duplicated from Java class android.content.res.Configuration.
+    static const jint kScreenLayoutRoundMask = 0x300;
+    static const jint kScreenLayoutRoundShift = 8;
+
     config.mcc = (uint16_t)mcc;
     config.mnc = (uint16_t)mnc;
     config.orientation = (uint8_t)orientation;
@@ -632,6 +636,13 @@
     config.uiMode = (uint8_t)uiMode;
     config.sdkVersion = (uint16_t)sdkVersion;
     config.minorVersion = 0;
+
+    // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer
+    // in C++. We must extract the round qualifier out of the Java screenLayout and put it
+    // into screenLayout2.
+    config.screenLayout2 =
+            (uint8_t)((screenLayout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
+
     am->setConfiguration(config, locale8);
 
     if (locale != NULL) env->ReleaseStringUTFChars(locale, locale8);
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index b15caeb..5130e6c 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -1132,6 +1132,24 @@
     // chars. Interpreted in conjunction with the locale field.
     char localeVariant[8];
 
+    enum {
+        // screenLayout2 bits for round/notround.
+        MASK_SCREENROUND = 0x03,
+        SCREENROUND_ANY = ACONFIGURATION_SCREENROUND_ANY,
+        SCREENROUND_NO = ACONFIGURATION_SCREENROUND_NO,
+        SCREENROUND_YES = ACONFIGURATION_SCREENROUND_YES,
+    };
+
+    // An extension of screenConfig.
+    union {
+        struct {
+            uint8_t screenLayout2;      // Contains round/notround qualifier.
+            uint8_t screenConfigPad1;   // Reserved padding.
+            uint16_t screenConfigPad2;  // Reserved padding.
+        };
+        uint32_t screenConfig2;
+    };
+
     void copyFromDeviceNoSwap(const ResTable_config& o);
     
     void copyFromDtoH(const ResTable_config& o);
@@ -1160,6 +1178,7 @@
         CONFIG_SCREEN_LAYOUT = ACONFIGURATION_SCREEN_LAYOUT,
         CONFIG_UI_MODE = ACONFIGURATION_UI_MODE,
         CONFIG_LAYOUTDIR = ACONFIGURATION_LAYOUTDIR,
+        CONFIG_SCREEN_ROUND = ACONFIGURATION_SCREEN_ROUND,
     };
     
     // Compare two configuration, returning CONFIG_* flags set for each value
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 2ae7b08..a95db9f 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -1894,6 +1894,8 @@
     if (diff != 0) return diff;
     diff = (int32_t)(screenLayout - o.screenLayout);
     if (diff != 0) return diff;
+    diff = (int32_t)(screenLayout2 - o.screenLayout2);
+    if (diff != 0) return diff;
     diff = (int32_t)(uiMode - o.uiMode);
     if (diff != 0) return diff;
     diff = (int32_t)(smallestScreenWidthDp - o.smallestScreenWidthDp);
@@ -1951,6 +1953,9 @@
     if (screenLayout != o.screenLayout) {
         return screenLayout < o.screenLayout ? -1 : 1;
     }
+    if (screenLayout2 != o.screenLayout2) {
+        return screenLayout2 < o.screenLayout2 ? -1 : 1;
+    }
     if (uiMode != o.uiMode) {
         return uiMode < o.uiMode ? -1 : 1;
     }
@@ -1975,6 +1980,7 @@
     if (version != o.version) diffs |= CONFIG_VERSION;
     if ((screenLayout & MASK_LAYOUTDIR) != (o.screenLayout & MASK_LAYOUTDIR)) diffs |= CONFIG_LAYOUTDIR;
     if ((screenLayout & ~MASK_LAYOUTDIR) != (o.screenLayout & ~MASK_LAYOUTDIR)) diffs |= CONFIG_SCREEN_LAYOUT;
+    if ((screenLayout2 & MASK_SCREENROUND) != (o.screenLayout2 & MASK_SCREENROUND)) diffs |= CONFIG_SCREEN_ROUND;
     if (uiMode != o.uiMode) diffs |= CONFIG_UI_MODE;
     if (smallestScreenWidthDp != o.smallestScreenWidthDp) diffs |= CONFIG_SMALLEST_SCREEN_SIZE;
     if (screenSizeDp != o.screenSizeDp) diffs |= CONFIG_SCREEN_SIZE;
@@ -2080,6 +2086,13 @@
         }
     }
 
+    if (screenLayout2 || o.screenLayout2) {
+        if (((screenLayout2^o.screenLayout2) & MASK_SCREENROUND) != 0) {
+            if (!(screenLayout2 & MASK_SCREENROUND)) return false;
+            if (!(o.screenLayout2 & MASK_SCREENROUND)) return true;
+        }
+    }
+
     if (orientation != o.orientation) {
         if (!orientation) return false;
         if (!o.orientation) return true;
@@ -2267,6 +2280,13 @@
             }
         }
 
+        if (screenLayout2 || o.screenLayout2) {
+            if (((screenLayout2^o.screenLayout2) & MASK_SCREENROUND) != 0 &&
+                    (requested->screenLayout2 & MASK_SCREENROUND)) {
+                return screenLayout2 & MASK_SCREENROUND;
+            }
+        }
+
         if ((orientation != o.orientation) && requested->orientation) {
             return (orientation);
         }
@@ -2480,6 +2500,15 @@
             return false;
         }
     }
+
+    if (screenConfig2 != 0) {
+        const int screenRound = screenLayout2 & MASK_SCREENROUND;
+        const int setScreenRound = settings.screenLayout2 & MASK_SCREENROUND;
+        if (screenRound != 0 && screenRound != setScreenRound) {
+            return false;
+        }
+    }
+
     if (screenSizeDp != 0) {
         if (screenWidthDp != 0 && screenWidthDp > settings.screenWidthDp) {
             if (kDebugTableSuperNoisy) {
@@ -2770,6 +2799,20 @@
                 break;
         }
     }
+    if ((screenLayout2&MASK_SCREENROUND) != 0) {
+        if (res.size() > 0) res.append("-");
+        switch (screenLayout2&MASK_SCREENROUND) {
+            case SCREENROUND_NO:
+                res.append("notround");
+                break;
+            case SCREENROUND_YES:
+                res.append("round");
+                break;
+            default:
+                res.appendFormat("screenRound=%d", dtohs(screenLayout2&MASK_SCREENROUND));
+                break;
+        }
+    }
     if (orientation != ORIENTATION_ANY) {
         if (res.size() > 0) res.append("-");
         switch (orientation) {
diff --git a/libs/androidfw/tests/Config_test.cpp b/libs/androidfw/tests/Config_test.cpp
index ef30df4..738947a 100644
--- a/libs/androidfw/tests/Config_test.cpp
+++ b/libs/androidfw/tests/Config_test.cpp
@@ -100,4 +100,82 @@
     ASSERT_EQ(expectedBest, selectBest(deviceConfig, configs));
 }
 
+TEST(ConfigTest, shouldMatchRoundQualifier) {
+    ResTable_config deviceConfig;
+    memset(&deviceConfig, 0, sizeof(deviceConfig));
+
+    ResTable_config roundConfig;
+    memset(&roundConfig, 0, sizeof(roundConfig));
+    roundConfig.screenLayout2 = ResTable_config::SCREENROUND_YES;
+
+    EXPECT_FALSE(roundConfig.match(deviceConfig));
+
+    deviceConfig.screenLayout2 = ResTable_config::SCREENROUND_YES;
+
+    EXPECT_TRUE(roundConfig.match(deviceConfig));
+
+    deviceConfig.screenLayout2 = ResTable_config::SCREENROUND_NO;
+
+    EXPECT_FALSE(roundConfig.match(deviceConfig));
+
+    ResTable_config notRoundConfig;
+    memset(&notRoundConfig, 0, sizeof(notRoundConfig));
+    notRoundConfig.screenLayout2 = ResTable_config::SCREENROUND_NO;
+
+    EXPECT_TRUE(notRoundConfig.match(deviceConfig));
+}
+
+TEST(ConfigTest, RoundQualifierShouldHaveStableSortOrder) {
+    ResTable_config defaultConfig;
+    memset(&defaultConfig, 0, sizeof(defaultConfig));
+
+    ResTable_config longConfig = defaultConfig;
+    longConfig.screenLayout = ResTable_config::SCREENLONG_YES;
+
+    ResTable_config longRoundConfig = longConfig;
+    longRoundConfig.screenLayout2 = ResTable_config::SCREENROUND_YES;
+
+    ResTable_config longRoundPortConfig = longConfig;
+    longRoundPortConfig.orientation = ResTable_config::ORIENTATION_PORT;
+
+    EXPECT_TRUE(longConfig.compare(longRoundConfig) < 0);
+    EXPECT_TRUE(longConfig.compareLogical(longRoundConfig) < 0);
+    EXPECT_TRUE(longRoundConfig.compare(longConfig) > 0);
+    EXPECT_TRUE(longRoundConfig.compareLogical(longConfig) > 0);
+
+    EXPECT_TRUE(longRoundConfig.compare(longRoundPortConfig) < 0);
+    EXPECT_TRUE(longRoundConfig.compareLogical(longRoundPortConfig) < 0);
+    EXPECT_TRUE(longRoundPortConfig.compare(longRoundConfig) > 0);
+    EXPECT_TRUE(longRoundPortConfig.compareLogical(longRoundConfig) > 0);
+}
+
+TEST(ConfigTest, ScreenShapeHasCorrectDiff) {
+    ResTable_config defaultConfig;
+    memset(&defaultConfig, 0, sizeof(defaultConfig));
+
+    ResTable_config roundConfig = defaultConfig;
+    roundConfig.screenLayout2 = ResTable_config::SCREENROUND_YES;
+
+    EXPECT_EQ(defaultConfig.diff(roundConfig), ResTable_config::CONFIG_SCREEN_ROUND);
+}
+
+TEST(ConfigTest, RoundIsMoreSpecific) {
+    ResTable_config deviceConfig;
+    memset(&deviceConfig, 0, sizeof(deviceConfig));
+    deviceConfig.screenLayout2 = ResTable_config::SCREENROUND_YES;
+    deviceConfig.screenLayout = ResTable_config::SCREENLONG_YES;
+
+    ResTable_config targetConfigA;
+    memset(&targetConfigA, 0, sizeof(targetConfigA));
+
+    ResTable_config targetConfigB = targetConfigA;
+    targetConfigB.screenLayout = ResTable_config::SCREENLONG_YES;
+
+    ResTable_config targetConfigC = targetConfigB;
+    targetConfigC.screenLayout2 = ResTable_config::SCREENROUND_YES;
+
+    EXPECT_TRUE(targetConfigB.isBetterThan(targetConfigA, &deviceConfig));
+    EXPECT_TRUE(targetConfigC.isBetterThan(targetConfigB, &deviceConfig));
+}
+
 }  // namespace android.
diff --git a/native/android/configuration.cpp b/native/android/configuration.cpp
index 74cf80e..77237ae 100644
--- a/native/android/configuration.cpp
+++ b/native/android/configuration.cpp
@@ -101,6 +101,10 @@
             >> ResTable_config::SHIFT_SCREENLONG;
 }
 
+int32_t AConfiguration_getScreenRound(AConfiguration* config) {
+    return (config->screenLayout2&ResTable_config::MASK_SCREENROUND);
+}
+
 int32_t AConfiguration_getUiModeType(AConfiguration* config) {
     return config->uiMode&ResTable_config::MASK_UI_MODE_TYPE;
 }
@@ -192,6 +196,11 @@
             | ((screenLong<<ResTable_config::SHIFT_SCREENLONG)&ResTable_config::MASK_SCREENLONG);
 }
 
+void AConfiguration_setScreenRound(AConfiguration* config, int32_t screenRound) {
+    config->screenLayout2 = (config->screenLayout2&~ResTable_config::MASK_SCREENROUND)
+            | (screenRound&ResTable_config::MASK_SCREENROUND);
+}
+
 void AConfiguration_setUiModeType(AConfiguration* config, int32_t uiModeType) {
     config->uiMode = (config->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
             | (uiModeType&ResTable_config::MASK_UI_MODE_TYPE);
diff --git a/tools/aapt/AaptConfig.cpp b/tools/aapt/AaptConfig.cpp
index ede9e99..b12867a 100644
--- a/tools/aapt/AaptConfig.cpp
+++ b/tools/aapt/AaptConfig.cpp
@@ -123,6 +123,14 @@
         part = parts[index].string();
     }
 
+    if (parseScreenRound(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
     if (parseOrientation(part, &config)) {
         index++;
         if (index == N) {
@@ -241,7 +249,9 @@
     }
 
     uint16_t minSdk = 0;
-    if (config->density == ResTable_config::DENSITY_ANY) {
+    if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) {
+        minSdk = SDK_MNC;
+    } else if (config->density == ResTable_config::DENSITY_ANY) {
         minSdk = SDK_LOLLIPOP;
     } else if (config->smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY
             || config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY
@@ -395,7 +405,26 @@
                 | ResTable_config::SCREENLONG_NO;
         return true;
     }
+    return false;
+}
 
+bool parseScreenRound(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->screenLayout2 =
+                (out->screenLayout2&~ResTable_config::MASK_SCREENROUND)
+                | ResTable_config::SCREENROUND_ANY;
+        return true;
+    } else if (strcmp(name, "round") == 0) {
+        if (out) out->screenLayout2 =
+                (out->screenLayout2&~ResTable_config::MASK_SCREENROUND)
+                | ResTable_config::SCREENROUND_YES;
+        return true;
+    } else if (strcmp(name, "notround") == 0) {
+        if (out) out->screenLayout2 =
+                (out->screenLayout2&~ResTable_config::MASK_SCREENROUND)
+                | ResTable_config::SCREENROUND_NO;
+        return true;
+    }
     return false;
 }
 
diff --git a/tools/aapt/AaptConfig.h b/tools/aapt/AaptConfig.h
index f73a5081..04c763f 100644
--- a/tools/aapt/AaptConfig.h
+++ b/tools/aapt/AaptConfig.h
@@ -60,6 +60,7 @@
 bool parseScreenHeightDp(const char* str, android::ResTable_config* out = NULL);
 bool parseScreenLayoutSize(const char* str, android::ResTable_config* out = NULL);
 bool parseScreenLayoutLong(const char* str, android::ResTable_config* out = NULL);
+bool parseScreenRound(const char* name, android::ResTable_config* out = NULL);
 bool parseOrientation(const char* str, android::ResTable_config* out = NULL);
 bool parseUiModeType(const char* str, android::ResTable_config* out = NULL);
 bool parseUiModeNight(const char* str, android::ResTable_config* out = NULL);
diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h
index 4e0fe10..16e622a 100644
--- a/tools/aapt/SdkConstants.h
+++ b/tools/aapt/SdkConstants.h
@@ -38,6 +38,7 @@
     SDK_KITKAT_WATCH = 20,
     SDK_LOLLIPOP = 21,
     SDK_LOLLIPOP_MR1 = 22,
+    SDK_MNC = 23,
 };
 
 #endif // H_AAPT_SDK_CONSTANTS
diff --git a/tools/aapt/tests/AaptConfig_test.cpp b/tools/aapt/tests/AaptConfig_test.cpp
index 7618974..8bb38ba 100644
--- a/tools/aapt/tests/AaptConfig_test.cpp
+++ b/tools/aapt/tests/AaptConfig_test.cpp
@@ -19,6 +19,7 @@
 
 #include "AaptConfig.h"
 #include "ConfigDescription.h"
+#include "SdkConstants.h"
 #include "TestHelper.h"
 
 using android::String8;
@@ -82,3 +83,18 @@
     EXPECT_TRUE(TestParse("car", &config));
     EXPECT_EQ(android::ResTable_config::UI_MODE_TYPE_CAR, config.uiMode);
 }
+
+TEST(AaptConfigTest, TestParsingRoundQualifier) {
+    ConfigDescription config;
+    EXPECT_TRUE(TestParse("round", &config));
+    EXPECT_EQ(android::ResTable_config::SCREENROUND_YES,
+              config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND);
+    EXPECT_EQ(SDK_MNC, config.sdkVersion);
+    EXPECT_EQ(String8("round-v23"), config.toString());
+
+    EXPECT_TRUE(TestParse("notround", &config));
+    EXPECT_EQ(android::ResTable_config::SCREENROUND_NO,
+              config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND);
+    EXPECT_EQ(SDK_MNC, config.sdkVersion);
+    EXPECT_EQ(String8("notround-v23"), config.toString());
+}