Add CTS tests for FDs backed by DBs and byte[]

The tests are partly based on code from
mdodd's change I4f21bdbe22be3c09eb8732a4e9b90b90d55d4941

Bug: http://b/issue?id=2595601
Change-Id: I47ab427a45cd584f958d27e4e9ac98b670e76d74
diff --git a/tests/tests/database/src/android/database/cts/DatabaseUtilsTest.java b/tests/tests/database/src/android/database/cts/DatabaseUtilsTest.java
index c67002c..3c552e8 100644
--- a/tests/tests/database/src/android/database/cts/DatabaseUtilsTest.java
+++ b/tests/tests/database/src/android/database/cts/DatabaseUtilsTest.java
@@ -33,11 +33,15 @@
 import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteStatement;
 import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
 import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
 import java.io.PrintStream;
 
 @TestTargetClass(android.database.DatabaseUtils.class)
@@ -63,6 +67,8 @@
         assertNotNull(mDatabase);
         mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, " +
                 "name TEXT, age INTEGER, address TEXT);");
+        mDatabase.execSQL(
+                "CREATE TABLE blob_test (_id INTEGER PRIMARY KEY, name TEXT, data BLOB)");
     }
 
     @Override
@@ -719,11 +725,98 @@
 
         args = new String[] { "1000" }; // NO people can be older than this.
         try {
-            DatabaseUtils.stringForQuery(statement, args);
+            DatabaseUtils.blobFileDescriptorForQuery(statement, args);
             fail("should throw SQLiteDoneException");
         } catch (SQLiteDoneException e) {
             // expected
         }
         statement.close();
     }
+
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "blobFileDescriptorForQuery",
+            args = {android.database.sqlite.SQLiteDatabase.class, java.lang.String.class,
+                    java.lang.String[].class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "blobFileDescriptorForQuery",
+            args = {android.database.sqlite.SQLiteStatement.class, java.lang.String[].class}
+        )
+    })
+    public void testBlobFileDescriptorForQuery() throws Exception {
+        String data1 = "5300FEFF";
+        String data2 = "DECAFBAD";
+        mDatabase.execSQL("INSERT INTO blob_test (name, data) VALUES ('Mike', X'" + data1 + "')");
+
+        String query = "SELECT data FROM blob_test";
+        assertFileDescriptorContent(parseBlob(data1),
+                        DatabaseUtils.blobFileDescriptorForQuery(mDatabase, query, null));
+
+        mDatabase.execSQL("INSERT INTO blob_test (name, data) VALUES ('Jack', X'" + data2 + "');");
+        query = "SELECT data FROM blob_test WHERE name = ?";
+        String[] args = new String[] { "Jack" };
+        assertFileDescriptorContent(parseBlob(data2),
+                DatabaseUtils.blobFileDescriptorForQuery(mDatabase, query, args));
+
+        args = new String[] { "No such name" };
+        try {
+            DatabaseUtils.stringForQuery(mDatabase, query, args);
+            fail("should throw SQLiteDoneException");
+        } catch (SQLiteDoneException e) {
+            // expected
+        }
+
+        query = "SELECT data FROM blob_test WHERE name = ?;";
+        SQLiteStatement statement = mDatabase.compileStatement(query);
+        args = new String[] { "Mike" };
+        assertFileDescriptorContent(parseBlob(data1),
+                DatabaseUtils.blobFileDescriptorForQuery(statement, args));
+
+        args = new String[] { "No such name" };
+        try {
+            DatabaseUtils.blobFileDescriptorForQuery(statement, args);
+            fail("should throw SQLiteDoneException");
+        } catch (SQLiteDoneException e) {
+            // expected
+        }
+        statement.close();
+    }
+
+    private static byte[] parseBlob(String src) {
+        int len = src.length();
+        byte[] result = new byte[len / 2];
+
+        for (int i = 0; i < len/2; i++) {
+            int val;
+            char c1 = src.charAt(i*2);
+            char c2 = src.charAt(i*2+1);
+            int val1 = Character.digit(c1, 16);
+            int val2 = Character.digit(c2, 16);
+            val = (val1 << 4) | val2;
+            result[i] = (byte)val;
+        }
+        return result;
+    }
+
+    private static void assertFileDescriptorContent(byte[] expected, ParcelFileDescriptor fd)
+            throws IOException {
+        assertInputStreamContent(expected, new ParcelFileDescriptor.AutoCloseInputStream(fd));
+    }
+
+    private static void assertInputStreamContent(byte[] expected, InputStream is)
+            throws IOException {
+        try {
+            byte[] observed = new byte[expected.length];
+            int count = is.read(observed);
+            assertEquals(expected.length, count);
+            assertEquals(-1, is.read());
+            MoreAsserts.assertEquals(expected, observed);
+        } finally {
+            is.close();
+        }
+    }
+
 }
diff --git a/tests/tests/database/src/android/database/sqlite/cts/SQLiteStatementTest.java b/tests/tests/database/src/android/database/sqlite/cts/SQLiteStatementTest.java
index 5f4fe7a..828045a 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/SQLiteStatementTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteStatementTest.java
@@ -21,6 +21,7 @@
 import dalvik.annotation.TestTargetNew;
 import dalvik.annotation.TestTargets;
 
+import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
@@ -28,13 +29,27 @@
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteDoneException;
 import android.database.sqlite.SQLiteStatement;
+import android.os.ParcelFileDescriptor;
 import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+
+import java.io.IOException;
+import java.io.InputStream;
 
 @TestTargetClass(android.database.sqlite.SQLiteStatement.class)
 public class SQLiteStatementTest extends AndroidTestCase {
     private static final String STRING1 = "this is a test";
     private static final String STRING2 = "another test";
 
+    private static final byte[][] BLOBS = new byte [][] {
+        parseBlob("86FADCF1A820666AEBD0789F47932151A2EF734269E8AC4E39630AB60519DFD8"),
+        new byte[0],
+        null,
+        parseBlob("00"),
+        parseBlob("FF"),
+        parseBlob("D7B500FECF25F7A4D83BF823D3858690790F2526013DE6CAE9A69170E2A1E47238"),
+    };
+
     private static final String DATABASE_NAME = "database_test.db";
 
     private static final int CURRENT_DATABASE_VERSION = 42;
@@ -61,6 +76,16 @@
         mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
     }
 
+    private void populateBlobTable() {
+        mDatabase.execSQL("CREATE TABLE blob_test (_id INTEGER PRIMARY KEY, data BLOB)");
+        for (int i = 0; i < BLOBS.length; i++) {
+            ContentValues values = new ContentValues();
+            values.put("_id", i);
+            values.put("data", BLOBS[i]);
+            mDatabase.insert("blob_test", null, values);
+        }
+    }
+
     @TestTargetNew(
         level = TestLevel.COMPLETE,
         method = "executeUpdateDelete",
@@ -217,4 +242,177 @@
 
         statement.close();
     }
+
+    @TestTargetNew(
+        level = TestLevel.PARTIAL_COMPLETE,
+        method = "simpleQueryForBlobFileDescriptor",
+        args = {}
+    )
+    public void testSimpleQueryForBlobFileDescriptorSuccessNormal() throws IOException {
+        doTestSimpleQueryForBlobFileDescriptorSuccess(0);
+    }
+
+    @TestTargetNew(
+        level = TestLevel.PARTIAL_COMPLETE,
+        method = "simpleQueryForBlobFileDescriptor",
+        args = {}
+    )
+    public void testSimpleQueryForBlobFileDescriptorSuccessEmpty() throws IOException {
+        doTestSimpleQueryForBlobFileDescriptorSuccess(1);
+    }
+
+    @TestTargetNew(
+        level = TestLevel.PARTIAL_COMPLETE,
+        method = "simpleQueryForBlobFileDescriptor",
+        args = {}
+    )
+    public void testSimpleQueryForBlobFileDescriptorSuccessNull() {
+        populateBlobTable();
+
+        String sql = "SELECT data FROM blob_test WHERE _id = " + 2;
+        SQLiteStatement stm = mDatabase.compileStatement(sql);
+        assertNull(stm.simpleQueryForBlobFileDescriptor());
+    }
+
+    @TestTargetNew(
+        level = TestLevel.PARTIAL_COMPLETE,
+        method = "simpleQueryForBlobFileDescriptor",
+        args = {}
+    )
+    public void testSimpleQueryForBlobFileDescriptorSuccess00() throws IOException {
+        doTestSimpleQueryForBlobFileDescriptorSuccess(3);
+    }
+
+    @TestTargetNew(
+        level = TestLevel.PARTIAL_COMPLETE,
+        method = "simpleQueryForBlobFileDescriptor",
+        args = {}
+    )
+    public void testSimpleQueryForBlobFileDescriptorSuccessFF() throws IOException {
+        doTestSimpleQueryForBlobFileDescriptorSuccess(4);
+    }
+
+    @TestTargetNew(
+        level = TestLevel.PARTIAL_COMPLETE,
+        method = "simpleQueryForBlobFileDescriptor",
+        args = {}
+    )
+    public void testSimpleQueryForBlobFileDescriptorSuccessEmbeddedNul() throws IOException {
+        doTestSimpleQueryForBlobFileDescriptorSuccess(5);
+    }
+
+    private void doTestSimpleQueryForBlobFileDescriptorSuccess(int i) throws IOException {
+        populateBlobTable();
+
+        String sql = "SELECT data FROM blob_test WHERE _id = " + i;
+        SQLiteStatement stm = mDatabase.compileStatement(sql);
+        ParcelFileDescriptor fd = stm.simpleQueryForBlobFileDescriptor();
+        assertFileDescriptorContent(BLOBS[i], fd);
+    }
+
+    @TestTargetNew(
+        level = TestLevel.PARTIAL_COMPLETE,
+        method = "simpleQueryForBlobFileDescriptor",
+        args = {}
+    )
+    public void testSimpleQueryForBlobFileDescriptorSuccessParam() throws IOException {
+        populateBlobTable();
+
+        String sql = "SELECT data FROM blob_test WHERE _id = ?";
+        SQLiteStatement stm = mDatabase.compileStatement(sql);
+        stm.bindLong(1, 0);
+        ParcelFileDescriptor fd = stm.simpleQueryForBlobFileDescriptor();
+        assertFileDescriptorContent(BLOBS[0], fd);
+    }
+
+    @TestTargetNew(
+        level = TestLevel.PARTIAL_COMPLETE,
+        method = "simpleQueryForBlobFileDescriptor",
+        args = {}
+    )
+    public void testGetBlobFailureNoParam() throws Exception {
+        populateBlobTable();
+
+        String sql = "SELECT data FROM blob_test WHERE _id = 100";
+        SQLiteStatement stm = mDatabase.compileStatement(sql);
+        ParcelFileDescriptor fd = null;
+        SQLiteDoneException expectedException = null;
+        try {
+            fd = stm.simpleQueryForBlobFileDescriptor();
+        } catch (SQLiteDoneException ex) {
+            expectedException = ex;
+        } finally {
+            if (fd != null) {
+                fd.close();
+                fd = null;
+            }
+        }
+        assertNotNull("Should have thrown SQLiteDoneException", expectedException);
+    }
+
+    @TestTargetNew(
+        level = TestLevel.PARTIAL_COMPLETE,
+        method = "simpleQueryForBlobFileDescriptor",
+        args = {}
+    )
+    public void testGetBlobFailureParam() throws Exception {
+        populateBlobTable();
+
+        String sql = "SELECT data FROM blob_test WHERE _id = ?";
+        SQLiteStatement stm = mDatabase.compileStatement(sql);
+        stm.bindLong(1, 100);
+        ParcelFileDescriptor fd = null;
+        SQLiteDoneException expectedException = null;
+        try {
+            fd = stm.simpleQueryForBlobFileDescriptor();
+        } catch (SQLiteDoneException ex) {
+            expectedException = ex;
+        } finally {
+            if (fd != null) {
+                fd.close();
+                fd = null;
+            }
+        }
+        assertNotNull("Should have thrown SQLiteDoneException", expectedException);
+    }
+
+    /*
+     * Convert string of hex digits to byte array.
+     * Results are undefined for poorly formed string.
+     *
+     * @param src hex string
+     */
+    private static byte[] parseBlob(String src) {
+        int len = src.length();
+        byte[] result = new byte[len / 2];
+
+        for (int i = 0; i < len/2; i++) {
+            int val;
+            char c1 = src.charAt(i*2);
+            char c2 = src.charAt(i*2+1);
+            int val1 = Character.digit(c1, 16);
+            int val2 = Character.digit(c2, 16);
+            val = (val1 << 4) | val2;
+            result[i] = (byte)val;
+        }
+        return result;
+    }
+
+    private static void assertFileDescriptorContent(byte[] expected, ParcelFileDescriptor fd)
+            throws IOException {
+        assertInputStreamContent(expected, new ParcelFileDescriptor.AutoCloseInputStream(fd));
+    }
+
+    private static void assertInputStreamContent(byte[] expected, InputStream is)
+            throws IOException {
+        try {
+            byte[] observed = new byte[expected.length];
+            int count = is.read(observed);
+            assertEquals(expected.length, count);
+            assertEquals(-1, is.read());
+            MoreAsserts.assertEquals(expected, observed);
+        } finally {
+            is.close();
+        }
+    }
 }
diff --git a/tests/tests/os/src/android/os/cts/ParcelFileDescriptorTest.java b/tests/tests/os/src/android/os/cts/ParcelFileDescriptorTest.java
index ce6658f..15c52cf 100644
--- a/tests/tests/os/src/android/os/cts/ParcelFileDescriptorTest.java
+++ b/tests/tests/os/src/android/os/cts/ParcelFileDescriptorTest.java
@@ -28,9 +28,12 @@
 import android.os.Parcelable;
 import android.os.ParcelFileDescriptor.AutoCloseInputStream;
 import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
 
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.InetAddress;
 import java.net.ServerSocket;
@@ -110,6 +113,45 @@
     }
 
     @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "fromData",
+            args = {byte[].class}
+    )
+    public void testFromData() throws IOException {
+        assertNull(ParcelFileDescriptor.fromData(null, null));
+        byte[] data = new byte[] { 0 };
+        assertFileDescriptorContent(data, ParcelFileDescriptor.fromData(data, null));
+        data = new byte[] { 0, 1, 2, 3 };
+        assertFileDescriptorContent(data, ParcelFileDescriptor.fromData(data, null));
+        data = new byte[0];
+        assertFileDescriptorContent(data, ParcelFileDescriptor.fromData(data, null));
+
+        // Check that modifying the data does not modify the data in the FD
+        data = new byte[] { 0, 1, 2, 3 };
+        ParcelFileDescriptor pfd = ParcelFileDescriptor.fromData(data, null);
+        data[1] = 42;
+        assertFileDescriptorContent(new byte[] { 0, 1, 2, 3 }, pfd);
+    }
+
+    private static void assertFileDescriptorContent(byte[] expected, ParcelFileDescriptor fd)
+        throws IOException {
+        assertInputStreamContent(expected, new ParcelFileDescriptor.AutoCloseInputStream(fd));
+    }
+
+    private static void assertInputStreamContent(byte[] expected, InputStream is)
+            throws IOException {
+        try {
+            byte[] observed = new byte[expected.length];
+            int count = is.read(observed);
+            assertEquals(expected.length, count);
+            assertEquals(-1, is.read());
+            MoreAsserts.assertEquals(expected, observed);
+        } finally {
+            is.close();
+        }
+    }
+
+    @TestTargetNew(
         level = TestLevel.COMPLETE,
         method = "toString",
         args = {}