Merge "New CTS tests for unstable content provider references." into jb-dev
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 1fde1ab..e96acda 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -819,7 +819,10 @@
         <provider android:name="android.content.cts.DummyProvider"
             android:authorities="android.content.cts.dummyprovider"
             android:multiprocess="true" />
-
+        <provider android:name="android.content.cts.MockRemoteContentProvider"
+            android:authorities="remotectstest"
+            android:process=":remoteprovider" android:multiprocess="false" />
+        
         <activity android:name="android.app.cts.ChildTabActivity" android:label="ChildTabActivity" />
 
         <activity android:name="android.app.cts.LauncherActivityStub"
diff --git a/tests/src/android/content/cts/MockContentProvider.java b/tests/src/android/content/cts/MockContentProvider.java
index 29c64805..de82c0d 100644
--- a/tests/src/android/content/cts/MockContentProvider.java
+++ b/tests/src/android/content/cts/MockContentProvider.java
@@ -16,6 +16,12 @@
 
 package android.content.cts;
 
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
 import java.util.HashMap;
 
 import android.content.ContentProvider;
@@ -23,37 +29,47 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.UriMatcher;
+import android.content.ContentProvider.PipeDataWriter;
+import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
 import android.database.SQLException;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
 import android.text.TextUtils;
+import android.util.Log;
 
-public class MockContentProvider extends ContentProvider {
+public class MockContentProvider extends ContentProvider
+        implements PipeDataWriter<String> {
 
     private SQLiteOpenHelper mOpenHelper;
 
-    private static final String AUTHORITY = "ctstest";
-    private static final String DBNAME = "ctstest.db";
+    private static final String DEFAULT_AUTHORITY = "ctstest";
+    private static final String DEFAULT_DBNAME = "ctstest.db";
     private static final int DBVERSION = 2;
 
-    private static final UriMatcher URL_MATCHER;
     private static final int TESTTABLE1 = 1;
     private static final int TESTTABLE1_ID = 2;
     private static final int TESTTABLE1_CROSS = 3;
     private static final int TESTTABLE2 = 4;
     private static final int TESTTABLE2_ID = 5;
+    private static final int SELF_ID = 6;
+    private static final int CRASH_ID = 6;
 
-    private static HashMap<String, String> CTSDBTABLE1_LIST_PROJECTION_MAP;
-    private static HashMap<String, String> CTSDBTABLE2_LIST_PROJECTION_MAP;
+    private final String mAuthority;
+    private final String mDbName;
+    private final UriMatcher URL_MATCHER;
+    private HashMap<String, String> CTSDBTABLE1_LIST_PROJECTION_MAP;
+    private HashMap<String, String> CTSDBTABLE2_LIST_PROJECTION_MAP;
 
     private static class DatabaseHelper extends SQLiteOpenHelper {
 
-        DatabaseHelper(Context context) {
-            super(context, DBNAME, null, DBVERSION);
+        DatabaseHelper(Context context, String dbname) {
+            super(context, dbname, null, DBVERSION);
         }
 
         @Override
@@ -75,9 +91,46 @@
         }
     }
 
+    public MockContentProvider() {
+        this(DEFAULT_AUTHORITY, DEFAULT_DBNAME);
+    }
+
+    public MockContentProvider(String authority, String dbName) {
+        mAuthority = authority;
+        mDbName = dbName;
+
+        URL_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+        URL_MATCHER.addURI(mAuthority, "testtable1", TESTTABLE1);
+        URL_MATCHER.addURI(mAuthority, "testtable1/#", TESTTABLE1_ID);
+        URL_MATCHER.addURI(mAuthority, "testtable1/cross", TESTTABLE1_CROSS);
+        URL_MATCHER.addURI(mAuthority, "testtable2", TESTTABLE2);
+        URL_MATCHER.addURI(mAuthority, "testtable2/#", TESTTABLE2_ID);
+        URL_MATCHER.addURI(mAuthority, "self", SELF_ID);
+        URL_MATCHER.addURI(mAuthority, "crash", CRASH_ID);
+
+        CTSDBTABLE1_LIST_PROJECTION_MAP = new HashMap<String, String>();
+        CTSDBTABLE1_LIST_PROJECTION_MAP.put("_id", "_id");
+        CTSDBTABLE1_LIST_PROJECTION_MAP.put("key", "key");
+        CTSDBTABLE1_LIST_PROJECTION_MAP.put("value", "value");
+
+        CTSDBTABLE2_LIST_PROJECTION_MAP = new HashMap<String, String>();
+        CTSDBTABLE2_LIST_PROJECTION_MAP.put("_id", "_id");
+        CTSDBTABLE2_LIST_PROJECTION_MAP.put("key", "key");
+        CTSDBTABLE2_LIST_PROJECTION_MAP.put("value", "value");
+    }
+
     @Override
     public boolean onCreate() {
-        mOpenHelper = new DatabaseHelper(getContext());
+        mOpenHelper = new DatabaseHelper(getContext(), mDbName);
+        if (android.provider.Settings.System.getInt(getContext().getContentResolver(),
+                "__cts_crash_on_launch", 0) != 0) {
+            // The test case wants us to crash our process on first launch.
+            // Well, okay then!
+            Log.i("MockContentProvider", "TEST IS CRASHING SELF, CROSS FINGERS!");
+            android.provider.Settings.System.putInt(getContext().getContentResolver(),
+                    "__cts_crash_on_launch", 0);
+            android.os.Process.killProcess(android.os.Process.myPid());
+        }
         return true;
     }
 
@@ -110,6 +163,12 @@
                     (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
                     selectionArgs);
             break;
+        case SELF_ID:
+            // Wha...?  Delete ME?!?  O.K.!
+            Log.i("MockContentProvider", "Delete self requested!");
+            count = 1;
+            android.os.Process.killProcess(android.os.Process.myPid());
+            break;
         default:
             throw new IllegalArgumentException("Unknown URL " + uri);
         }
@@ -155,11 +214,11 @@
         switch (URL_MATCHER.match(uri)) {
         case TESTTABLE1:
             table = "TestTable1";
-            testUri = Uri.parse("content://" + AUTHORITY + "/testtable1");
+            testUri = Uri.parse("content://" + mAuthority + "/testtable1");
             break;
         case TESTTABLE2:
             table = "TestTable2";
-            testUri = Uri.parse("content://" + AUTHORITY + "/testtable2");
+            testUri = Uri.parse("content://" + mAuthority + "/testtable2");
             break;
         default:
             throw new IllegalArgumentException("Unknown URL " + uri);
@@ -217,6 +276,20 @@
             qb.appendWhere("_id=" + uri.getPathSegments().get(1));
             break;
 
+        case CRASH_ID:
+            if (android.provider.Settings.System.getInt(getContext().getContentResolver(),
+                    "__cts_crash_on_launch", 0) != 0) {
+                // The test case wants us to crash while querying.
+                // Well, okay then!
+                Log.i("MockContentProvider", "TEST IS CRASHING SELF, CROSS FINGERS!");
+                android.provider.Settings.System.putInt(getContext().getContentResolver(),
+                        "__cts_crash_on_launch", 0);
+                android.os.Process.killProcess(android.os.Process.myPid());
+            }
+            qb.setTables("TestTable1");
+            qb.setProjectionMap(CTSDBTABLE1_LIST_PROJECTION_MAP);
+            break;
+
         default:
             throw new IllegalArgumentException("Unknown URL " + uri);
         }
@@ -274,22 +347,71 @@
         return count;
     }
 
-    static {
-        URL_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
-        URL_MATCHER.addURI(AUTHORITY, "testtable1", TESTTABLE1);
-        URL_MATCHER.addURI(AUTHORITY, "testtable1/#", TESTTABLE1_ID);
-        URL_MATCHER.addURI(AUTHORITY, "testtable1/cross", TESTTABLE1_CROSS);
-        URL_MATCHER.addURI(AUTHORITY, "testtable2", TESTTABLE2);
-        URL_MATCHER.addURI(AUTHORITY, "testtable2/#", TESTTABLE2_ID);
+    @Override
+    public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
+        switch (URL_MATCHER.match(uri)) {
+            case CRASH_ID:
+                if (android.provider.Settings.System.getInt(getContext().getContentResolver(),
+                        "__cts_crash_on_launch", 0) != 0) {
+                    // The test case wants us to crash while querying.
+                    // Well, okay then!
+                    Log.i("MockContentProvider", "TEST IS CRASHING SELF, CROSS FINGERS!");
+                    android.provider.Settings.System.putInt(getContext().getContentResolver(),
+                            "__cts_crash_on_launch", 0);
+                    android.os.Process.killProcess(android.os.Process.myPid());
+                }
+                return new AssetFileDescriptor(
+                        openPipeHelper(uri, null, null,
+                                "This is the openAssetFile test data!", this), 0,
+                        AssetFileDescriptor.UNKNOWN_LENGTH);
 
-        CTSDBTABLE1_LIST_PROJECTION_MAP = new HashMap<String, String>();
-        CTSDBTABLE1_LIST_PROJECTION_MAP.put("_id", "_id");
-        CTSDBTABLE1_LIST_PROJECTION_MAP.put("key", "key");
-        CTSDBTABLE1_LIST_PROJECTION_MAP.put("value", "value");
+            default:
+                return super.openAssetFile(uri, mode);
+        }
+    }
 
-        CTSDBTABLE2_LIST_PROJECTION_MAP = new HashMap<String, String>();
-        CTSDBTABLE2_LIST_PROJECTION_MAP.put("_id", "_id");
-        CTSDBTABLE2_LIST_PROJECTION_MAP.put("key", "key");
-        CTSDBTABLE2_LIST_PROJECTION_MAP.put("value", "value");
+    @Override
+    public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
+            throws FileNotFoundException {
+        switch (URL_MATCHER.match(uri)) {
+            case CRASH_ID:
+                if (android.provider.Settings.System.getInt(getContext().getContentResolver(),
+                        "__cts_crash_on_launch", 0) != 0) {
+                    // The test case wants us to crash while querying.
+                    // Well, okay then!
+                    Log.i("MockContentProvider", "TEST IS CRASHING SELF, CROSS FINGERS!");
+                    android.provider.Settings.System.putInt(getContext().getContentResolver(),
+                            "__cts_crash_on_launch", 0);
+                    android.os.Process.killProcess(android.os.Process.myPid());
+                }
+                return new AssetFileDescriptor(
+                        openPipeHelper(uri, null, null,
+                                "This is the openTypedAssetFile test data!", this), 0,
+                        AssetFileDescriptor.UNKNOWN_LENGTH);
+
+            default:
+                return super.openTypedAssetFile(uri, mimeTypeFilter, opts);
+        }
+    }
+
+    @Override
+    public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType, Bundle opts,
+            String args) {
+        FileOutputStream fout = new FileOutputStream(output.getFileDescriptor());
+        PrintWriter pw = null;
+        try {
+            pw = new PrintWriter(new OutputStreamWriter(fout, "UTF-8"));
+            pw.print(args);
+        } catch (UnsupportedEncodingException e) {
+            Log.w("MockContentProvider", "Ooops", e);
+        } finally {
+            if (pw != null) {
+                pw.flush();
+            }
+            try {
+                fout.close();
+            } catch (IOException e) {
+            }
+        }
     }
 }
diff --git a/tests/src/android/content/cts/MockRemoteContentProvider.java b/tests/src/android/content/cts/MockRemoteContentProvider.java
new file mode 100644
index 0000000..fc43701
--- /dev/null
+++ b/tests/src/android/content/cts/MockRemoteContentProvider.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+package android.content.cts;
+
+public class MockRemoteContentProvider extends MockContentProvider {
+    public MockRemoteContentProvider() {
+        super("remotectstest", "remotectstest.db");
+    }
+}
diff --git a/tests/tests/content/src/android/content/cts/ContentResolverTest.java b/tests/tests/content/src/android/content/cts/ContentResolverTest.java
index 3276c8e..22c2faa 100644
--- a/tests/tests/content/src/android/content/cts/ContentResolverTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentResolverTest.java
@@ -20,6 +20,7 @@
 
 
 import android.accounts.Account;
+import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -33,11 +34,15 @@
 import android.os.CancellationSignal;
 import android.os.OperationCanceledException;
 import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
 import android.test.AndroidTestCase;
+import android.util.Log;
 
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.OutputStream;
 
 public class ContentResolverTest extends AndroidTestCase {
@@ -50,6 +55,16 @@
     private static final Uri TABLE1_CROSS_URI =
             Uri.parse("content://" + AUTHORITY + "/testtable1/cross");
     private static final Uri TABLE2_URI = Uri.parse("content://" + AUTHORITY + "/testtable2/");
+    private static final Uri SELF_URI = Uri.parse("content://" + AUTHORITY + "/self/");
+    private static final Uri CRASH_URI = Uri.parse("content://" + AUTHORITY + "/crash/");
+
+    private static final String REMOTE_AUTHORITY = "remotectstest";
+    private static final Uri REMOTE_TABLE1_URI = Uri.parse("content://"
+                + REMOTE_AUTHORITY + "/testtable1/");
+    private static final Uri REMOTE_SELF_URI = Uri.parse("content://"
+                + REMOTE_AUTHORITY + "/self/");
+    private static final Uri REMOTE_CRASH_URI = Uri.parse("content://"
+            + REMOTE_AUTHORITY + "/crash/");
 
     private static final Account ACCOUNT = new Account("cts", "cts");
 
@@ -73,20 +88,25 @@
         mContext = getContext();
         mContentResolver = mContext.getContentResolver();
 
+        android.provider.Settings.System.putInt(mContentResolver, "__cts_crash_on_launch", 0);
+
         // add three rows to database when every test case start.
         ContentValues values = new ContentValues();
 
         values.put(COLUMN_KEY_NAME, KEY1);
         values.put(COLUMN_VALUE_NAME, VALUE1);
         mContentResolver.insert(TABLE1_URI, values);
+        mContentResolver.insert(REMOTE_TABLE1_URI, values);
 
         values.put(COLUMN_KEY_NAME, KEY2);
         values.put(COLUMN_VALUE_NAME, VALUE2);
         mContentResolver.insert(TABLE1_URI, values);
+        mContentResolver.insert(REMOTE_TABLE1_URI, values);
 
         values.put(COLUMN_KEY_NAME, KEY3);
         values.put(COLUMN_VALUE_NAME, VALUE3);
         mContentResolver.insert(TABLE1_URI, values);
+        mContentResolver.insert(REMOTE_TABLE1_URI, values);
     }
 
     @Override
@@ -95,6 +115,10 @@
         if ( null != mCursor && !mCursor.isClosed() ) {
             mCursor.close();
         }
+        mContentResolver.delete(REMOTE_TABLE1_URI, null, null);
+        if ( null != mCursor && !mCursor.isClosed() ) {
+            mCursor.close();
+        }
         super.tearDown();
     }
 
@@ -102,6 +126,125 @@
         assertNotNull(mContentResolver);
     }
 
+    public void testCrashOnLaunch() {
+        // This test is going to make sure that the platform deals correctly
+        // with a content provider process going away while a client is waiting
+        // for it to come up.
+        // First, we need to make sure our provider process is gone.  Goodbye!
+        ContentProviderClient client = mContentResolver.acquireContentProviderClient(
+                REMOTE_AUTHORITY);
+        // We are going to do something wrong here...  release the client first,
+        // so the act of killing it doesn't kill our own process.
+        client.release();
+        try {
+            client.delete(REMOTE_SELF_URI, null, null);
+        } catch (RemoteException e) {
+        }
+        // Now make sure the thing is actually gone.
+        boolean gone = true;
+        try {
+            client.getType(REMOTE_TABLE1_URI);
+            gone = false;
+        } catch (RemoteException e) {
+        }
+        if (!gone) {
+            fail("Content provider process is not gone!");
+        }
+        try {
+            android.provider.Settings.System.putInt(mContentResolver, "__cts_crash_on_launch", 1);
+            String type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
+            assertEquals(android.provider.Settings.System.getInt(mContentResolver,
+                "__cts_crash_on_launch", 0), 0);
+            assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
+        } finally {
+            android.provider.Settings.System.putInt(mContentResolver, "__cts_crash_on_launch", 0);
+        }
+    }
+
+    public void testUnstableToStableRefs() {
+        // Get an unstable refrence on the remote content provider.
+        ContentProviderClient uClient = mContentResolver.acquireUnstableContentProviderClient(
+                REMOTE_AUTHORITY);
+        // Verify we can access it.
+        String type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
+        assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
+
+        // Get a stable reference on the remote content provider.
+        ContentProviderClient sClient = mContentResolver.acquireContentProviderClient(
+                REMOTE_AUTHORITY);
+        // Verify we can still access it.
+        type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
+        assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
+
+        // Release unstable reference.
+        uClient.release();
+        // Verify we can still access it.
+        type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
+        assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
+
+        // Release stable reference, removing last ref.
+        sClient.release();
+        // Kill it.  Note that a bug at this point where it causes our own
+        // process to be killed will result in the entire test failing.
+        try {
+            Log.i("ContentResolverTest",
+                    "Killing remote client -- if test process goes away, that is why!");
+            uClient.delete(REMOTE_SELF_URI, null, null);
+        } catch (RemoteException e) {
+        }
+        // Make sure the remote client is actually gone.
+        boolean gone = true;
+        try {
+            sClient.getType(REMOTE_TABLE1_URI);
+            gone = false;
+        } catch (RemoteException e) {
+        }
+        if (!gone) {
+            fail("Content provider process is not gone!");
+        }
+    }
+
+    public void testStableToUnstableRefs() {
+        // Get a stable reference on the remote content provider.
+        ContentProviderClient sClient = mContentResolver.acquireContentProviderClient(
+                REMOTE_AUTHORITY);
+        // Verify we can still access it.
+        String type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
+        assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
+        
+        // Get an unstable refrence on the remote content provider.
+        ContentProviderClient uClient = mContentResolver.acquireUnstableContentProviderClient(
+                REMOTE_AUTHORITY);
+        // Verify we can access it.
+        type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
+        assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
+
+        // Release stable reference, leaving only an unstable ref.
+        sClient.release();
+
+        // Kill it.  Note that a bug at this point where it causes our own
+        // process to be killed will result in the entire test failing.
+        try {
+            Log.i("ContentResolverTest",
+                    "Killing remote client -- if test process goes away, that is why!");
+            uClient.delete(REMOTE_SELF_URI, null, null);
+        } catch (RemoteException e) {
+        }
+        // Make sure the remote client is actually gone.
+        boolean gone = true;
+        try {
+            uClient.getType(REMOTE_TABLE1_URI);
+            gone = false;
+        } catch (RemoteException e) {
+        }
+        if (!gone) {
+            fail("Content provider process is not gone!");
+        }
+
+        // Release unstable reference.
+        uClient.release();
+    }
+
     public void testGetType() {
         String type1 = mContentResolver.getType(TABLE1_URI);
         assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
@@ -120,6 +263,43 @@
         }
     }
 
+    public void testUnstableGetType() {
+        // Get an unstable refrence on the remote content provider.
+        ContentProviderClient client = mContentResolver.acquireUnstableContentProviderClient(
+                REMOTE_AUTHORITY);
+        // Verify we can access it.
+        String type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
+        assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
+
+        // Kill it.  Note that a bug at this point where it causes our own
+        // process to be killed will result in the entire test failing.
+        try {
+            Log.i("ContentResolverTest",
+                    "Killing remote client -- if test process goes away, that is why!");
+            client.delete(REMOTE_SELF_URI, null, null);
+        } catch (RemoteException e) {
+        }
+        // Make sure the remote client is actually gone.
+        boolean gone = true;
+        try {
+            client.getType(REMOTE_TABLE1_URI);
+            gone = false;
+        } catch (RemoteException e) {
+        }
+        if (!gone) {
+            fail("Content provider process is not gone!");
+        }
+
+        // Now the remote client is gone, can we recover?
+        // Release our old reference.
+        client.release();
+        // Get a new reference.
+        client = mContentResolver.acquireUnstableContentProviderClient(REMOTE_AUTHORITY);
+        // Verify we can access it.
+        type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
+        assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
+    }
+
     public void testQuery() {
         mCursor = mContentResolver.query(TABLE1_URI, null, null, null, null);
 
@@ -170,6 +350,32 @@
         }
     }
 
+    public void testCrashingQuery() {
+        try {
+            android.provider.Settings.System.putInt(mContentResolver, "__cts_crash_on_launch", 1);
+            mCursor = mContentResolver.query(REMOTE_CRASH_URI, null, null, null, null);
+            assertEquals(android.provider.Settings.System.getInt(mContentResolver,
+                "__cts_crash_on_launch", 0), 0);
+        } finally {
+            android.provider.Settings.System.putInt(mContentResolver, "__cts_crash_on_launch", 0);
+        }
+
+        assertNotNull(mCursor);
+        assertEquals(3, mCursor.getCount());
+        assertEquals(3, mCursor.getColumnCount());
+
+        mCursor.moveToLast();
+        assertEquals(3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
+        assertEquals(KEY3, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
+        assertEquals(VALUE3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
+
+        mCursor.moveToPrevious();
+        assertEquals(2, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
+        assertEquals(KEY2, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
+        assertEquals(VALUE2, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
+        mCursor.close();
+    }
+
     public void testCancelableQuery_WhenNotCanceled_ReturnsResultSet() {
         CancellationSignal cancellationSignal = new CancellationSignal();
 
@@ -334,6 +540,98 @@
         }
     }
 
+    private String consumeAssetFileDescriptor(AssetFileDescriptor afd)
+            throws IOException {
+        FileInputStream stream = null;
+        try {
+            stream = afd.createInputStream();
+            InputStreamReader reader = new InputStreamReader(stream, "UTF-8");
+
+            // Got it...  copy the stream into a local string and return it.
+            StringBuilder builder = new StringBuilder(128);
+            char[] buffer = new char[8192];
+            int len;
+            while ((len=reader.read(buffer)) > 0) {
+                builder.append(buffer, 0, len);
+            }
+            return builder.toString();
+
+        } finally {
+            if (stream != null) {
+                try {
+                    stream.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+        
+    }
+
+    public void testCrashingOpenAssetFileDescriptor() throws IOException {
+        AssetFileDescriptor afd = null;
+        try {
+            android.provider.Settings.System.putInt(mContentResolver, "__cts_crash_on_launch", 1);
+            afd = mContentResolver.openAssetFileDescriptor(REMOTE_CRASH_URI, "rw");
+            assertEquals(android.provider.Settings.System.getInt(mContentResolver,
+                    "__cts_crash_on_launch", 0), 0);
+            assertNotNull(afd);
+            String str = consumeAssetFileDescriptor(afd);
+            afd = null;
+            assertEquals(str, "This is the openAssetFile test data!");
+        } finally {
+            android.provider.Settings.System.putInt(mContentResolver, "__cts_crash_on_launch", 0);
+            if (afd != null) {
+                afd.close();
+            }
+        }
+
+        // Make sure a content provider crash at this point won't hurt us.
+        ContentProviderClient uClient = mContentResolver.acquireUnstableContentProviderClient(
+                REMOTE_AUTHORITY);
+        // Kill it.  Note that a bug at this point where it causes our own
+        // process to be killed will result in the entire test failing.
+        try {
+            Log.i("ContentResolverTest",
+                    "Killing remote client -- if test process goes away, that is why!");
+            uClient.delete(REMOTE_SELF_URI, null, null);
+        } catch (RemoteException e) {
+        }
+        uClient.release();
+    }
+
+    public void testCrashingOpenTypedAssetFileDescriptor() throws IOException {
+        AssetFileDescriptor afd = null;
+        try {
+            android.provider.Settings.System.putInt(mContentResolver, "__cts_crash_on_launch", 1);
+            afd = mContentResolver.openTypedAssetFileDescriptor(
+                    REMOTE_CRASH_URI, "text/plain", null);
+            assertEquals(android.provider.Settings.System.getInt(mContentResolver,
+                    "__cts_crash_on_launch", 0), 0);
+            assertNotNull(afd);
+            String str = consumeAssetFileDescriptor(afd);
+            afd = null;
+            assertEquals(str, "This is the openTypedAssetFile test data!");
+        } finally {
+            android.provider.Settings.System.putInt(mContentResolver, "__cts_crash_on_launch", 0);
+            if (afd != null) {
+                afd.close();
+            }
+        }
+
+        // Make sure a content provider crash at this point won't hurt us.
+        ContentProviderClient uClient = mContentResolver.acquireUnstableContentProviderClient(
+                REMOTE_AUTHORITY);
+        // Kill it.  Note that a bug at this point where it causes our own
+        // process to be killed will result in the entire test failing.
+        try {
+            Log.i("ContentResolverTest",
+                    "Killing remote client -- if test process goes away, that is why!");
+            uClient.delete(REMOTE_SELF_URI, null, null);
+        } catch (RemoteException e) {
+        }
+        uClient.release();
+    }
+
     public void testOpenFileDescriptor() throws IOException {
         Uri uri = Uri.parse(ContentResolver.SCHEME_FILE + "://" +
                 getContext().getCacheDir().getAbsolutePath() +