add API to Cursor to get column value type

Change-Id: I3ef1bcdb2eb1c45f68e829ccb6e3ecde28076591
diff --git a/api/current.xml b/api/current.xml
index 2b31a64..bafbb68 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -55658,6 +55658,19 @@
 <parameter name="column" type="int">
 </parameter>
 </method>
+<method name="getType"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="column" type="int">
+</parameter>
+</method>
 <method name="getUpdatedField"
  return="java.lang.Object"
  abstract="false"
@@ -56681,6 +56694,19 @@
 <parameter name="columnIndex" type="int">
 </parameter>
 </method>
+<method name="getType"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
 <method name="getWantsAllOnMoveCalls"
  return="boolean"
  abstract="true"
@@ -56921,6 +56947,61 @@
 <parameter name="observer" type="android.database.DataSetObserver">
 </parameter>
 </method>
+<field name="FIELD_TYPE_BLOB"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="FIELD_TYPE_FLOAT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="FIELD_TYPE_INTEGER"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="FIELD_TYPE_NULL"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="FIELD_TYPE_STRING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 </interface>
 <class name="CursorIndexOutOfBoundsException"
  extends="java.lang.IndexOutOfBoundsException"
@@ -57278,6 +57359,21 @@
 <parameter name="col" type="int">
 </parameter>
 </method>
+<method name="getType"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="row" type="int">
+</parameter>
+<parameter name="col" type="int">
+</parameter>
+</method>
 <method name="isBlob"
  return="boolean"
  abstract="false"
@@ -57285,7 +57381,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="row" type="int">
@@ -57300,7 +57396,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="row" type="int">
@@ -57315,7 +57411,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="row" type="int">
@@ -57330,7 +57426,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="row" type="int">
@@ -57345,7 +57441,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="row" type="int">
@@ -57756,6 +57852,19 @@
 <parameter name="columnIndex" type="int">
 </parameter>
 </method>
+<method name="getType"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
 <method name="getWantsAllOnMoveCalls"
  return="boolean"
  abstract="false"
@@ -155557,6 +155666,19 @@
 <parameter name="columnIndex" type="int">
 </parameter>
 </method>
+<method name="getType"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
 <method name="getWantsAllOnMoveCalls"
  return="boolean"
  abstract="false"
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index 6170bae..b1f33ec 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -51,6 +51,10 @@
     abstract public double getDouble(int column);
     abstract public boolean isNull(int column);
 
+    public int getType(int column) {
+        throw new UnsupportedOperationException();
+    }
+
     // TODO implement getBlob in all cursor types
     public byte[] getBlob(int column) {
         throw new UnsupportedOperationException("getBlob is not supported");
diff --git a/core/java/android/database/AbstractWindowedCursor.java b/core/java/android/database/AbstractWindowedCursor.java
index 27a02e2..e47d9ce 100644
--- a/core/java/android/database/AbstractWindowedCursor.java
+++ b/core/java/android/database/AbstractWindowedCursor.java
@@ -149,63 +149,36 @@
             }
         }
 
-        return mWindow.isNull(mPos, columnIndex);
+        return mWindow.getType(mPos, columnIndex) == Cursor.FIELD_TYPE_NULL;
     }
 
-    public boolean isBlob(int columnIndex)
+    public boolean isBlob(int columnIndex) {
+        return getType(columnIndex) == Cursor.FIELD_TYPE_BLOB;
+    }
+
+    public boolean isString(int columnIndex) {
+        return getType(columnIndex) == Cursor.FIELD_TYPE_STRING;
+    }
+
+    public boolean isLong(int columnIndex) {
+        return getType(columnIndex) == Cursor.FIELD_TYPE_INTEGER;
+    }
+
+    public boolean isFloat(int columnIndex) {
+        return getType(columnIndex) == Cursor.FIELD_TYPE_FLOAT;
+    }
+
+    @Override
+    public int getType(int columnIndex)
     {
         checkPosition();
-
         synchronized(mUpdatedRows) {
             if (isFieldUpdated(columnIndex)) {
-                Object object = getUpdatedField(columnIndex);
-                return object == null || object instanceof byte[];
+                return DatabaseUtils.getTypeOfObject(getUpdatedField(columnIndex));
             }
         }
 
-        return mWindow.isBlob(mPos, columnIndex);
-    }
-
-    public boolean isString(int columnIndex)
-    {
-        checkPosition();
-
-        synchronized(mUpdatedRows) {
-            if (isFieldUpdated(columnIndex)) {
-                Object object = getUpdatedField(columnIndex);
-                return object == null || object instanceof String;
-            }
-        }
-
-        return mWindow.isString(mPos, columnIndex);
-    }
-
-    public boolean isLong(int columnIndex)
-    {
-        checkPosition();
-
-        synchronized(mUpdatedRows) {
-            if (isFieldUpdated(columnIndex)) {
-                Object object = getUpdatedField(columnIndex);
-                return object != null && (object instanceof Integer || object instanceof Long);
-            }
-        }
-
-        return mWindow.isLong(mPos, columnIndex);
-    }
-
-    public boolean isFloat(int columnIndex)
-    {
-        checkPosition();
-
-        synchronized(mUpdatedRows) {
-            if (isFieldUpdated(columnIndex)) {
-                Object object = getUpdatedField(columnIndex);
-                return object != null && (object instanceof Float || object instanceof Double);
-            }
-        }
-
-        return mWindow.isFloat(mPos, columnIndex);
+        return mWindow.getType(mPos, columnIndex);
     }
 
     @Override
diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java
index fee658a..c03c586 100644
--- a/core/java/android/database/Cursor.java
+++ b/core/java/android/database/Cursor.java
@@ -30,6 +30,25 @@
  * threads should perform its own synchronization when using the Cursor.
  */
 public interface Cursor {
+    /*
+     * Values returned by {@link #getType(int)}.
+     * These should be consistent with the corresponding types defined in CursorWindow.h
+     */
+    /** Value returned by {@link #getType(int)} if the specified column is null */
+    static final int FIELD_TYPE_NULL = 0;
+
+    /** Value returned by {@link #getType(int)} if the specified  column type is integer */
+    static final int FIELD_TYPE_INTEGER = 1;
+
+    /** Value returned by {@link #getType(int)} if the specified column type is float */
+    static final int FIELD_TYPE_FLOAT = 2;
+
+    /** Value returned by {@link #getType(int)} if the specified column type is string */
+    static final int FIELD_TYPE_STRING = 3;
+
+    /** Value returned by {@link #getType(int)} if the specified column type is blob */
+    static final int FIELD_TYPE_BLOB = 4;
+
     /**
      * Returns the numbers of rows in the cursor.
      *
@@ -279,6 +298,27 @@
     double getDouble(int columnIndex);
 
     /**
+     * Returns data type of the given column's value.
+     * The preferred type of the column is returned but the data may be converted to other types
+     * as documented in the get-type methods such as {@link #getInt(int)}, {@link #getFloat(int)}
+     * etc.
+     *<p>
+     * Returned column types are
+     * <ul>
+     *   <li>{@link #FIELD_TYPE_NULL}</li>
+     *   <li>{@link #FIELD_TYPE_INTEGER}</li>
+     *   <li>{@link #FIELD_TYPE_FLOAT}</li>
+     *   <li>{@link #FIELD_TYPE_STRING}</li>
+     *   <li>{@link #FIELD_TYPE_BLOB}</li>
+     *</ul>
+     *</p>
+     *
+     * @param columnIndex the zero-based index of the target column.
+     * @return column value type
+     */
+    int getType(int columnIndex);
+
+    /**
      * Returns <code>true</code> if the value in the indicated column is null.
      *
      * @param columnIndex the zero-based index of the target column.
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index c756825..39889b9 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -217,14 +217,11 @@
      * @param row the row to read from, row - getStartPosition() being the actual row in the window
      * @param col the column to read from
      * @return {@code true} if given field is {@code NULL}
+     * @deprecated use {@link #getType(int, int)} instead
      */
+    @Deprecated
     public boolean isNull(int row, int col) {
-        acquireReference();
-        try {
-            return isNull_native(row - mStartPos, col);
-        } finally {
-            releaseReference();
-        }
+        return getType(row, col) == Cursor.FIELD_TYPE_NULL;
     }
     
     private native boolean isNull_native(int row, int col);
@@ -248,19 +245,42 @@
     private native byte[] getBlob_native(int row, int col);
 
     /**
+     * Returns data type of the given column's value.
+     *<p>
+     * Returned column types are
+     * <ul>
+     *   <li>{@link Cursor#FIELD_TYPE_NULL}</li>
+     *   <li>{@link Cursor#FIELD_TYPE_INTEGER}</li>
+     *   <li>{@link Cursor#FIELD_TYPE_FLOAT}</li>
+     *   <li>{@link Cursor#FIELD_TYPE_STRING}</li>
+     *   <li>{@link Cursor#FIELD_TYPE_BLOB}</li>
+     *</ul>
+     *</p>
+     *
+     * @param row the row to read from, row - getStartPosition() being the actual row in the window
+     * @param col the column to read from
+     * @return the value type
+     */
+    public int getType(int row, int col) {
+        acquireReference();
+        try {
+            return getType_native(row - mStartPos, col);
+        } finally {
+            releaseReference();
+        }
+    }
+
+    /**
      * Checks if a field contains either a blob or is null.
      *
      * @param row the row to read from, row - getStartPosition() being the actual row in the window
      * @param col the column to read from
      * @return {@code true} if given field is {@code NULL} or a blob
+     * @deprecated use {@link #getType(int, int)} instead
      */
+    @Deprecated
     public boolean isBlob(int row, int col) {
-        acquireReference();
-        try {
-            return isBlob_native(row - mStartPos, col);
-        } finally {
-            releaseReference();
-        }
+        return getType(row, col) == Cursor.FIELD_TYPE_BLOB;
     }
 
     /**
@@ -269,14 +289,11 @@
      * @param row the row to read from, row - getStartPosition() being the actual row in the window
      * @param col the column to read from
      * @return {@code true} if given field is a long
+     * @deprecated use {@link #getType(int, int)} instead
      */
+    @Deprecated
     public boolean isLong(int row, int col) {
-        acquireReference();
-        try {
-            return isInteger_native(row - mStartPos, col);
-        } finally {
-            releaseReference();
-        }
+        return getType(row, col) == Cursor.FIELD_TYPE_INTEGER;
     }
 
     /**
@@ -285,14 +302,11 @@
      * @param row the row to read from, row - getStartPosition() being the actual row in the window
      * @param col the column to read from
      * @return {@code true} if given field is a float
+     * @deprecated use {@link #getType(int, int)} instead
      */
+    @Deprecated
     public boolean isFloat(int row, int col) {
-        acquireReference();
-        try {
-            return isFloat_native(row - mStartPos, col);
-        } finally {
-            releaseReference();
-        }
+        return getType(row, col) == Cursor.FIELD_TYPE_FLOAT;
     }
 
     /**
@@ -301,20 +315,18 @@
      * @param row the row to read from, row - getStartPosition() being the actual row in the window
      * @param col the column to read from
      * @return {@code true} if given field is {@code NULL} or a String
+     * @deprecated use {@link #getType(int, int)} instead
      */
+    @Deprecated
     public boolean isString(int row, int col) {
-        acquireReference();
-        try {
-            return isString_native(row - mStartPos, col);
-        } finally {
-            releaseReference();
-        }
+        return getType(row, col) == Cursor.FIELD_TYPE_STRING;
     }
 
     private native boolean isBlob_native(int row, int col);
     private native boolean isString_native(int row, int col);
     private native boolean isInteger_native(int row, int col);
     private native boolean isFloat_native(int row, int col);
+    private native int getType_native(int row, int col);
 
     /**
      * Returns a String for the given field.
diff --git a/core/java/android/database/CursorWrapper.java b/core/java/android/database/CursorWrapper.java
index 633b2b3..2ac9470 100644
--- a/core/java/android/database/CursorWrapper.java
+++ b/core/java/android/database/CursorWrapper.java
@@ -130,6 +130,10 @@
         return mCursor.isLast();
     }
 
+    public int getType(int columnIndex) {
+        return mCursor.getType(columnIndex);
+    }
+
     public boolean isNull(int columnIndex) {
         return mCursor.isNull(columnIndex);
     }
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 9bfbb74..af93eee 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -193,6 +193,37 @@
     }
 
     /**
+     * Returns data type of the given object's value.
+     *<p>
+     * Returned values are
+     * <ul>
+     *   <li>{@link Cursor#FIELD_TYPE_NULL}</li>
+     *   <li>{@link Cursor#FIELD_TYPE_INTEGER}</li>
+     *   <li>{@link Cursor#FIELD_TYPE_FLOAT}</li>
+     *   <li>{@link Cursor#FIELD_TYPE_STRING}</li>
+     *   <li>{@link Cursor#FIELD_TYPE_BLOB}</li>
+     *</ul>
+     *</p>
+     *
+     * @param obj the object whose value type is to be returned
+     * @return object value type
+     * @hide
+     */
+    public static int getTypeOfObject(Object obj) {
+        if (obj == null) {
+            return Cursor.FIELD_TYPE_NULL;
+        } else if (obj instanceof byte[]) {
+            return Cursor.FIELD_TYPE_BLOB;
+        } else if (obj instanceof Float || obj instanceof Double) {
+            return Cursor.FIELD_TYPE_FLOAT;
+        } else if (obj instanceof Long || obj instanceof Integer) {
+            return Cursor.FIELD_TYPE_INTEGER;
+        } else {
+            return Cursor.FIELD_TYPE_STRING;
+        }
+    }
+
+    /**
      * Appends an SQL string to the given StringBuilder, including the opening
      * and closing single quotes. Any single quotes internal to sqlString will
      * be escaped.
diff --git a/core/java/android/database/MatrixCursor.java b/core/java/android/database/MatrixCursor.java
index d5c3a32..5c1b968 100644
--- a/core/java/android/database/MatrixCursor.java
+++ b/core/java/android/database/MatrixCursor.java
@@ -272,6 +272,11 @@
     }
 
     @Override
+    public int getType(int column) {
+        return DatabaseUtils.getTypeOfObject(get(column));
+    }
+
+    @Override
     public boolean isNull(int column) {
         return get(column) == null;
     }
diff --git a/core/java/android/database/MergeCursor.java b/core/java/android/database/MergeCursor.java
index cb6d7ac..2c25db7 100644
--- a/core/java/android/database/MergeCursor.java
+++ b/core/java/android/database/MergeCursor.java
@@ -129,6 +129,11 @@
     }
 
     @Override
+    public int getType(int column) {
+        return mCursor.getType(column);
+    }
+
+    @Override
     public boolean isNull(int column)
     {
         return mCursor.isNull(column);
diff --git a/core/java/com/android/internal/database/SortCursor.java b/core/java/com/android/internal/database/SortCursor.java
index 12248a2..0025512 100644
--- a/core/java/com/android/internal/database/SortCursor.java
+++ b/core/java/com/android/internal/database/SortCursor.java
@@ -218,6 +218,11 @@
     }
 
     @Override
+    public int getType(int column) {
+        return mCursor.getType(column);
+    }
+
+    @Override
     public boolean isNull(int column)
     {
         return mCursor.isNull(column);
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index fba6f0f..55f5252 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -652,6 +652,22 @@
     window->freeLastRow();
 }
 
+static jint getType_native(JNIEnv* env, jobject object, jint row, jint column)
+{
+    int32_t err;
+    CursorWindow * window = GET_WINDOW(env, object);
+    LOG_WINDOW("returning column type affinity for %d,%d from %p", row, column, window);
+
+    field_slot_t field;
+    err = window->read_field_slot(row, column, &field);
+    if (err != 0) {
+        throwExceptionWithRowCol(env, row, column);
+        return NULL;
+    }
+
+    return field.type;
+}
+
 static JNINativeMethod sMethods[] =
 {
      /* name, signature, funcPtr */
@@ -679,6 +695,7 @@
     {"isString_native", "(II)Z", (void *)isString_native},
     {"isFloat_native", "(II)Z", (void *)isFloat_native},
     {"isInteger_native", "(II)Z", (void *)isInteger_native},
+    {"getType_native", "(II)I", (void *)getType_native},
 };
 
 int register_android_database_CursorWindow(JNIEnv * env)
diff --git a/include/binder/CursorWindow.h b/include/binder/CursorWindow.h
index bda0d31..4fbff2a 100644
--- a/include/binder/CursorWindow.h
+++ b/include/binder/CursorWindow.h
@@ -1,16 +1,16 @@
 /*
  * Copyright (C) 2006 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 
+ * 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 
+ *      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 
+ * 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.
  */
 
@@ -80,11 +80,11 @@
     } data;
 } __attribute__((packed)) field_slot_t;
 
+#define FIELD_TYPE_NULL 0
 #define FIELD_TYPE_INTEGER 1
 #define FIELD_TYPE_FLOAT 2
 #define FIELD_TYPE_STRING 3
 #define FIELD_TYPE_BLOB 4
-#define FIELD_TYPE_NULL 5
 
 /**
  * This class stores a set of rows from a database in a buffer. The begining of the
@@ -170,7 +170,7 @@
     row_slot_t *        allocRowSlot();
 
     row_slot_t *        getRowSlot(int row);
-    
+
                         /**
                          * return NULL if Failed to find rowSlot or
                          * Invalid rowSlot
diff --git a/test-runner/src/android/test/mock/MockCursor.java b/test-runner/src/android/test/mock/MockCursor.java
index c817532..baa150a 100644
--- a/test-runner/src/android/test/mock/MockCursor.java
+++ b/test-runner/src/android/test/mock/MockCursor.java
@@ -191,4 +191,8 @@
     public void unregisterDataSetObserver(DataSetObserver observer) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
+
+    public int getType(int columnIndex) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
 }
\ No newline at end of file