Distinguish unspecified and explicit null values in resources

BUG: 17919345
Change-Id: Ic4f04f7dd0f986f58a749b5950d80c1cfdb074ea
diff --git a/api/current.txt b/api/current.txt
index fa02c4e..9513d90 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9260,6 +9260,7 @@
     method public int getType(int);
     method public boolean getValue(int, android.util.TypedValue);
     method public boolean hasValue(int);
+    method public boolean hasValueOrEmpty(int);
     method public int length();
     method public android.util.TypedValue peekValue(int);
     method public void recycle();
@@ -32172,6 +32173,8 @@
     field public static final int COMPLEX_UNIT_PX = 0; // 0x0
     field public static final int COMPLEX_UNIT_SHIFT = 0; // 0x0
     field public static final int COMPLEX_UNIT_SP = 2; // 0x2
+    field public static final int DATA_NULL_EMPTY = 1; // 0x1
+    field public static final int DATA_NULL_UNDEFINED = 0; // 0x0
     field public static final int DENSITY_DEFAULT = 0; // 0x0
     field public static final int DENSITY_NONE = 65535; // 0xffff
     field public static final int TYPE_ATTRIBUTE = 2; // 0x2
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 73b93c6..02602fb 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -807,6 +807,9 @@
 
     /**
      * Determines whether there is an attribute at <var>index</var>.
+     * <p>
+     * <strong>Note:</strong> If the attribute was set to {@code @empty} or
+     * {@code @undefined}, this method returns {@code false}.
      *
      * @param index Index of attribute to retrieve.
      *
@@ -824,6 +827,27 @@
     }
 
     /**
+     * Determines whether there is an attribute at <var>index</var>, returning
+     * {@code true} if the attribute was explicitly set to {@code @empty} and
+     * {@code false} only if the attribute was undefined.
+     *
+     * @param index Index of attribute to retrieve.
+     *
+     * @return True if the attribute has a value or is empty, false otherwise.
+     */
+    public boolean hasValueOrEmpty(int index) {
+        if (mRecycled) {
+            throw new RuntimeException("Cannot make calls to a recycled instance!");
+        }
+
+        index *= AssetManager.STYLE_NUM_ENTRIES;
+        final int[] data = mData;
+        final int type = data[index+AssetManager.STYLE_TYPE];
+        return type != TypedValue.TYPE_NULL
+                || data[index+AssetManager.STYLE_DATA] == TypedValue.DATA_NULL_EMPTY;
+    }
+
+    /**
      * Retrieve the raw TypedValue for the attribute at <var>index</var>
      * and return a temporary object holding its data.  This object is only
      * valid until the next call on to {@link TypedArray}.
diff --git a/core/java/android/util/TypedValue.java b/core/java/android/util/TypedValue.java
index 931fb81..d42ed03 100644
--- a/core/java/android/util/TypedValue.java
+++ b/core/java/android/util/TypedValue.java
@@ -139,6 +139,17 @@
     /* ------------------------------------------------------------ */
 
     /**
+     * {@link #TYPE_NULL} data indicating the value was not specified.
+     */
+    public static final int DATA_NULL_UNDEFINED = 0;
+    /**
+     * {@link #TYPE_NULL} data indicating the value was explicitly set to null.
+     */
+    public static final int DATA_NULL_EMPTY = 1;
+
+    /* ------------------------------------------------------------ */
+
+    /**
      * If {@link #density} is equal to this value, then the density should be
      * treated as the system's default density value: {@link DisplayMetrics#DENSITY_DEFAULT}.
      */
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 4859ee6..94098c9 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -1095,7 +1095,7 @@
         // coming from, first XML attributes, then XML style, then default
         // style, and finally the theme.
         value.dataType = Res_value::TYPE_NULL;
-        value.data = 0;
+        value.data = Res_value::DATA_NULL_UNDEFINED;
         typeSetFlags = 0;
         config.density = 0;
 
@@ -1157,6 +1157,7 @@
         if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
             DEBUG_STYLES(ALOGI("-> Setting to @null!"));
             value.dataType = Res_value::TYPE_NULL;
+            value.data = Res_value::DATA_NULL_UNDEFINED;
             block = -1;
         }
 
@@ -1319,7 +1320,7 @@
         // coming from, first XML attributes, then XML style, then default
         // style, and finally the theme.
         value.dataType = Res_value::TYPE_NULL;
-        value.data = 0;
+        value.data = Res_value::DATA_NULL_UNDEFINED;
         typeSetFlags = 0;
         config.density = 0;
 
@@ -1403,6 +1404,7 @@
         if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
             DEBUG_STYLES(ALOGI("-> Setting to @null!"));
             value.dataType = Res_value::TYPE_NULL;
+            value.data = Res_value::DATA_NULL_UNDEFINED;
             block = kXmlBlock;
         }
 
@@ -1512,7 +1514,7 @@
 
         // Try to find a value for this attribute...
         value.dataType = Res_value::TYPE_NULL;
-        value.data = 0;
+        value.data = Res_value::DATA_NULL_UNDEFINED;
         typeSetFlags = 0;
         config.density = 0;
 
@@ -1548,6 +1550,7 @@
         // Deal with the special @null value -- it turns back to TYPE_NULL.
         if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
             value.dataType = Res_value::TYPE_NULL;
+            value.data = Res_value::DATA_NULL_UNDEFINED;
         }
 
         //printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data);
@@ -1661,6 +1664,7 @@
         // Deal with the special @null value -- it turns back to TYPE_NULL.
         if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
             value.dataType = Res_value::TYPE_NULL;
+            value.data = Res_value::DATA_NULL_UNDEFINED;
         }
 
         //printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data);
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 6e03b3d..cc3ded5 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -1091,16 +1091,16 @@
     <!-- Dialog styles -->
 
     <style name="AlertDialog.Material" parent="AlertDialog">
-        <item name="fullDark">@null</item>
-        <item name="topDark">@null</item>
-        <item name="centerDark">@null</item>
-        <item name="bottomDark">@null</item>
-        <item name="fullBright">@null</item>
-        <item name="topBright">@null</item>
-        <item name="centerBright">@null</item>
-        <item name="bottomBright">@null</item>
-        <item name="bottomMedium">@null</item>
-        <item name="centerMedium">@null</item>
+        <item name="fullDark">@empty</item>
+        <item name="topDark">@empty</item>
+        <item name="centerDark">@empty</item>
+        <item name="bottomDark">@empty</item>
+        <item name="fullBright">@empty</item>
+        <item name="topBright">@empty</item>
+        <item name="centerBright">@empty</item>
+        <item name="bottomBright">@empty</item>
+        <item name="bottomMedium">@empty</item>
+        <item name="centerMedium">@empty</item>
         <item name="layout">@layout/alert_dialog_material</item>
         <item name="listLayout">@layout/select_dialog_material</item>
         <item name="progressLayout">@layout/progress_dialog_material</item>
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index ac5eca08..85e442da 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -253,7 +253,8 @@
         
     // Type of the data value.
     enum {
-        // Contains no data.
+        // The 'data' is either 0 or 1, specifying this resource is either
+        // undefined or empty, respectively.
         TYPE_NULL = 0x00,
         // The 'data' holds a ResTable_ref, a reference to another resource
         // table entry.
@@ -351,6 +352,14 @@
         COMPLEX_MANTISSA_MASK = 0xffffff
     };
 
+    // Possible data values for TYPE_NULL.
+    enum {
+        // The value is not defined.
+        DATA_NULL_UNDEFINED = 0,
+        // The value is explicitly defined as empty.
+        DATA_NULL_EMPTY = 1
+    };
+
     // The data for this item, as interpreted according to dataType.
     uint32_t data;
 
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index b5d2885..3bbca1a 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -4639,8 +4639,15 @@
 
         // It's a reference!
         if (len == 5 && s[1]=='n' && s[2]=='u' && s[3]=='l' && s[4]=='l') {
+            // Special case @null as undefined. This will be converted by
+            // AssetManager to TYPE_NULL with data DATA_NULL_UNDEFINED.
             outValue->data = 0;
             return true;
+        } else if (len == 6 && s[1]=='e' && s[2]=='m' && s[3]=='p' && s[4]=='t' && s[5]=='y') {
+            // Special case @empty as explicitly defined empty value.
+            outValue->dataType = Res_value::TYPE_NULL;
+            outValue->data = Res_value::DATA_NULL_EMPTY;
+            return true;
         } else {
             bool createIfNotFound = false;
             const char16_t* resourceRefName;
@@ -6251,7 +6258,14 @@
 void ResTable::print_value(const Package* pkg, const Res_value& value) const
 {
     if (value.dataType == Res_value::TYPE_NULL) {
-        printf("(null)\n");
+        if (value.data == Res_value::DATA_NULL_UNDEFINED) {
+            printf("(null)\n");
+        } else if (value.data == Res_value::DATA_NULL_EMPTY) {
+            printf("(null empty)\n");
+        } else {
+            // This should never happen.
+            printf("(null) 0x%08x\n", value.data);
+        }
     } else if (value.dataType == Res_value::TYPE_REFERENCE) {
         printf("(reference) 0x%08x\n", value.data);
     } else if (value.dataType == Res_value::TYPE_DYNAMIC_REFERENCE) {