Merge "Fixes to handleAppDiedLocked." into klp-dev
diff --git a/api/current.txt b/api/current.txt
index ff72c4a..2ed9c58 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -29459,6 +29459,7 @@
     ctor public AccessibilityNodeProvider();
     method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo(int);
     method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(java.lang.String, int);
+    method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
     method public boolean performAction(int, int, android.os.Bundle);
   }
 
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 3b88973..961ee57 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -758,6 +758,14 @@
             return true;
         }
 
+        case APP_NOT_RESPONDING_VIA_PROVIDER_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            appNotRespondingViaProvider(b);
+            reply.writeNoException();
+            return true;
+        }
+
         case REMOVE_CONTENT_PROVIDER_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             IBinder b = data.readStrongBinder();
@@ -2891,6 +2899,7 @@
         reply.recycle();
         return res;
     }
+
     public void unstableProviderDied(IBinder connection) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -2902,6 +2911,18 @@
         reply.recycle();
     }
 
+    @Override
+    public void appNotRespondingViaProvider(IBinder connection) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(connection);
+        mRemote.transact(APP_NOT_RESPONDING_VIA_PROVIDER_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
     public void removeContentProvider(IBinder connection, boolean stable) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 6605b5b..02faeac 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4653,6 +4653,19 @@
         }
     }
 
+    final void appNotRespondingViaProvider(IBinder provider) {
+        synchronized (mProviderMap) {
+            ProviderRefCount prc = mProviderRefCountMap.get(provider);
+            if (prc != null) {
+                try {
+                    ActivityManagerNative.getDefault()
+                            .appNotRespondingViaProvider(prc.holder.connection);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+    }
+
     private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,
             ContentProvider localProvider, IActivityManager.ContentProviderHolder holder) {
         final String auths[] = PATTERN_SEMICOLON.split(holder.info.authority);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 0ba2ac5..300424c 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2200,5 +2200,10 @@
         public void unstableProviderDied(IContentProvider icp) {
             mMainThread.handleUnstableProviderDied(icp.asBinder(), true);
         }
+
+        @Override
+        public void appNotRespondingViaProvider(IContentProvider icp) {
+            mMainThread.appNotRespondingViaProvider(icp.asBinder());
+        }
     }
 }
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 9a77377..dfea736 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -139,6 +139,7 @@
     public boolean refContentProvider(IBinder connection, int stableDelta, int unstableDelta)
             throws RemoteException;
     public void unstableProviderDied(IBinder connection) throws RemoteException;
+    public void appNotRespondingViaProvider(IBinder connection) throws RemoteException;
     public PendingIntent getRunningServiceControlPanel(ComponentName service)
             throws RemoteException;
     public ComponentName startService(IApplicationThread caller, Intent service,
@@ -691,4 +692,5 @@
     int TAKE_PERSISTABLE_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+179;
     int RELEASE_PERSISTABLE_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+180;
     int GET_PERSISTED_URI_PERMISSIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+181;
+    int APP_NOT_RESPONDING_VIA_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+182;
 }
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 0650798..cefc27f 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -16,15 +16,20 @@
 
 package android.content;
 
+import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.DeadObjectException;
+import android.os.Handler;
 import android.os.ICancellationSignal;
-import android.os.RemoteException;
+import android.os.Looper;
 import android.os.ParcelFileDescriptor;
-import android.content.res.AssetFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
 
 import dalvik.system.CloseGuard;
 
@@ -45,26 +50,64 @@
  * until you are finished with the data they have returned.
  */
 public class ContentProviderClient {
-    private final IContentProvider mContentProvider;
+    private static final String TAG = "ContentProviderClient";
+
+    @GuardedBy("ContentProviderClient.class")
+    private static Handler sAnrHandler;
+
     private final ContentResolver mContentResolver;
+    private final IContentProvider mContentProvider;
     private final String mPackageName;
     private final boolean mStable;
-    private boolean mReleased;
 
     private final CloseGuard mGuard = CloseGuard.get();
 
-    /**
-     * @hide
-     */
-    ContentProviderClient(ContentResolver contentResolver,
-            IContentProvider contentProvider, boolean stable) {
-        mContentProvider = contentProvider;
+    private long mAnrTimeout;
+    private NotRespondingRunnable mAnrRunnable;
+
+    private boolean mReleased;
+
+    /** {@hide} */
+    ContentProviderClient(
+            ContentResolver contentResolver, IContentProvider contentProvider, boolean stable) {
         mContentResolver = contentResolver;
+        mContentProvider = contentProvider;
         mPackageName = contentResolver.mPackageName;
         mStable = stable;
+
         mGuard.open("release");
     }
 
+    /** {@hide} */
+    public void setDetectNotResponding(long timeoutMillis) {
+        synchronized (ContentProviderClient.class) {
+            mAnrTimeout = timeoutMillis;
+
+            if (timeoutMillis > 0) {
+                if (mAnrRunnable == null) {
+                    mAnrRunnable = new NotRespondingRunnable();
+                }
+                if (sAnrHandler == null) {
+                    sAnrHandler = new Handler(Looper.getMainLooper(), null, true /* async */);
+                }
+            } else {
+                mAnrRunnable = null;
+            }
+        }
+    }
+
+    private void beforeRemote() {
+        if (mAnrRunnable != null) {
+            sAnrHandler.postDelayed(mAnrRunnable, mAnrTimeout);
+        }
+    }
+
+    private void afterRemote() {
+        if (mAnrRunnable != null) {
+            sAnrHandler.removeCallbacks(mAnrRunnable);
+        }
+    }
+
     /** See {@link ContentProvider#query ContentProvider.query} */
     public Cursor query(Uri url, String[] projection, String selection,
             String[] selectionArgs, String sortOrder) throws RemoteException {
@@ -72,28 +115,31 @@
     }
 
     /** See {@link ContentProvider#query ContentProvider.query} */
-    public Cursor query(Uri url, String[] projection, String selection,
-            String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal)
-                    throws RemoteException {
-        ICancellationSignal remoteCancellationSignal = null;
-        if (cancellationSignal != null) {
-            cancellationSignal.throwIfCanceled();
-            remoteCancellationSignal = mContentProvider.createCancellationSignal();
-            cancellationSignal.setRemote(remoteCancellationSignal);
-        }
+    public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder, CancellationSignal cancellationSignal) throws RemoteException {
+        beforeRemote();
         try {
-            return mContentProvider.query(mPackageName, url, projection, selection,  selectionArgs,
+            ICancellationSignal remoteCancellationSignal = null;
+            if (cancellationSignal != null) {
+                cancellationSignal.throwIfCanceled();
+                remoteCancellationSignal = mContentProvider.createCancellationSignal();
+                cancellationSignal.setRemote(remoteCancellationSignal);
+            }
+            return mContentProvider.query(mPackageName, url, projection, selection, selectionArgs,
                     sortOrder, remoteCancellationSignal);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
     /** See {@link ContentProvider#getType ContentProvider.getType} */
     public String getType(Uri url) throws RemoteException {
+        beforeRemote();
         try {
             return mContentProvider.getType(url);
         } catch (DeadObjectException e) {
@@ -101,11 +147,14 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
     /** See {@link ContentProvider#getStreamTypes ContentProvider.getStreamTypes} */
     public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException {
+        beforeRemote();
         try {
             return mContentProvider.getStreamTypes(url, mimeTypeFilter);
         } catch (DeadObjectException e) {
@@ -113,11 +162,14 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
     /** See {@link ContentProvider#canonicalize} */
     public final Uri canonicalize(Uri url) throws RemoteException {
+        beforeRemote();
         try {
             return mContentProvider.canonicalize(mPackageName, url);
         } catch (DeadObjectException e) {
@@ -125,11 +177,14 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
     /** See {@link ContentProvider#uncanonicalize} */
     public final Uri uncanonicalize(Uri url) throws RemoteException {
+        beforeRemote();
         try {
             return mContentProvider.uncanonicalize(mPackageName, url);
         } catch (DeadObjectException e) {
@@ -137,12 +192,14 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
     /** See {@link ContentProvider#insert ContentProvider.insert} */
-    public Uri insert(Uri url, ContentValues initialValues)
-            throws RemoteException {
+    public Uri insert(Uri url, ContentValues initialValues) throws RemoteException {
+        beforeRemote();
         try {
             return mContentProvider.insert(mPackageName, url, initialValues);
         } catch (DeadObjectException e) {
@@ -150,11 +207,14 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
     /** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */
     public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException {
+        beforeRemote();
         try {
             return mContentProvider.bulkInsert(mPackageName, url, initialValues);
         } catch (DeadObjectException e) {
@@ -162,12 +222,15 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
     /** See {@link ContentProvider#delete ContentProvider.delete} */
     public int delete(Uri url, String selection, String[] selectionArgs)
             throws RemoteException {
+        beforeRemote();
         try {
             return mContentProvider.delete(mPackageName, url, selection, selectionArgs);
         } catch (DeadObjectException e) {
@@ -175,12 +238,15 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
     /** See {@link ContentProvider#update ContentProvider.update} */
     public int update(Uri url, ContentValues values, String selection,
             String[] selectionArgs) throws RemoteException {
+        beforeRemote();
         try {
             return mContentProvider.update(mPackageName, url, values, selection, selectionArgs);
         } catch (DeadObjectException e) {
@@ -188,6 +254,8 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
@@ -212,19 +280,22 @@
      */
     public ParcelFileDescriptor openFile(Uri url, String mode, CancellationSignal signal)
             throws RemoteException, FileNotFoundException {
-        ICancellationSignal remoteSignal = null;
-        if (signal != null) {
-            signal.throwIfCanceled();
-            remoteSignal = mContentProvider.createCancellationSignal();
-            signal.setRemote(remoteSignal);
-        }
+        beforeRemote();
         try {
+            ICancellationSignal remoteSignal = null;
+            if (signal != null) {
+                signal.throwIfCanceled();
+                remoteSignal = mContentProvider.createCancellationSignal();
+                signal.setRemote(remoteSignal);
+            }
             return mContentProvider.openFile(mPackageName, url, mode, remoteSignal);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
@@ -249,19 +320,22 @@
      */
     public AssetFileDescriptor openAssetFile(Uri url, String mode, CancellationSignal signal)
             throws RemoteException, FileNotFoundException {
-        ICancellationSignal remoteSignal = null;
-        if (signal != null) {
-            signal.throwIfCanceled();
-            remoteSignal = mContentProvider.createCancellationSignal();
-            signal.setRemote(remoteSignal);
-        }
+        beforeRemote();
         try {
+            ICancellationSignal remoteSignal = null;
+            if (signal != null) {
+                signal.throwIfCanceled();
+                remoteSignal = mContentProvider.createCancellationSignal();
+                signal.setRemote(remoteSignal);
+            }
             return mContentProvider.openAssetFile(mPackageName, url, mode, remoteSignal);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
@@ -275,13 +349,14 @@
     public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri,
             String mimeType, Bundle opts, CancellationSignal signal)
             throws RemoteException, FileNotFoundException {
-        ICancellationSignal remoteSignal = null;
-        if (signal != null) {
-            signal.throwIfCanceled();
-            remoteSignal = mContentProvider.createCancellationSignal();
-            signal.setRemote(remoteSignal);
-        }
+        beforeRemote();
         try {
+            ICancellationSignal remoteSignal = null;
+            if (signal != null) {
+                signal.throwIfCanceled();
+                remoteSignal = mContentProvider.createCancellationSignal();
+                signal.setRemote(remoteSignal);
+            }
             return mContentProvider.openTypedAssetFile(
                     mPackageName, uri, mimeType, opts, remoteSignal);
         } catch (DeadObjectException e) {
@@ -289,12 +364,15 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
     /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */
     public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
             throws RemoteException, OperationApplicationException {
+        beforeRemote();
         try {
             return mContentProvider.applyBatch(mPackageName, operations);
         } catch (DeadObjectException e) {
@@ -302,12 +380,14 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
     /** See {@link ContentProvider#call(String, String, Bundle)} */
-    public Bundle call(String method, String arg, Bundle extras)
-            throws RemoteException {
+    public Bundle call(String method, String arg, Bundle extras) throws RemoteException {
+        beforeRemote();
         try {
             return mContentProvider.call(mPackageName, method, arg, extras);
         } catch (DeadObjectException e) {
@@ -315,6 +395,8 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
@@ -359,7 +441,7 @@
     }
 
     /** {@hide} */
-    public static void closeQuietly(ContentProviderClient client) {
+    public static void releaseQuietly(ContentProviderClient client) {
         if (client != null) {
             try {
                 client.release();
@@ -367,4 +449,12 @@
             }
         }
     }
+
+    private class NotRespondingRunnable implements Runnable {
+        @Override
+        public void run() {
+            Log.w(TAG, "Detected provider not responding: " + mContentProvider);
+            mContentResolver.appNotRespondingViaProvider(mContentProvider);
+        }
+    }
 }
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 95fb6858..916a6cd 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -287,6 +287,11 @@
     /** @hide */
     public abstract void unstableProviderDied(IContentProvider icp);
 
+    /** @hide */
+    public void appNotRespondingViaProvider(IContentProvider icp) {
+        throw new UnsupportedOperationException("appNotRespondingViaProvider");
+    }
+
     /**
      * Return the MIME type of the given content URL.
      *
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 85ec803..8bf6e4f 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -635,17 +635,18 @@
                 documentUri.getAuthority());
         try {
             return getDocumentThumbnail(client, documentUri, size, signal);
-        } catch (RemoteException e) {
+        } catch (Exception e) {
+            Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
             return null;
         } finally {
-            ContentProviderClient.closeQuietly(client);
+            ContentProviderClient.releaseQuietly(client);
         }
     }
 
     /** {@hide} */
     public static Bitmap getDocumentThumbnail(
             ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal)
-            throws RemoteException {
+            throws RemoteException, IOException {
         final Bundle openOpts = new Bundle();
         openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size);
 
@@ -693,9 +694,6 @@
                 }
                 return BitmapFactory.decodeFileDescriptor(fd, null, opts);
             }
-        } catch (IOException e) {
-            Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
-            return null;
         } finally {
             IoUtils.closeQuietly(afd);
         }
@@ -717,55 +715,53 @@
                 parentDocumentUri.getAuthority());
         try {
             return createDocument(client, parentDocumentUri, mimeType, displayName);
+        } catch (Exception e) {
+            Log.w(TAG, "Failed to create document", e);
+            return null;
         } finally {
-            ContentProviderClient.closeQuietly(client);
+            ContentProviderClient.releaseQuietly(client);
         }
     }
 
     /** {@hide} */
     public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri,
-            String mimeType, String displayName) {
+            String mimeType, String displayName) throws RemoteException {
         final Bundle in = new Bundle();
         in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri));
         in.putString(Document.COLUMN_MIME_TYPE, mimeType);
         in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
 
-        try {
-            final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
-            return buildDocumentUri(
-                    parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID));
-        } catch (Exception e) {
-            Log.w(TAG, "Failed to create document", e);
-            return null;
-        }
+        final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
+        return buildDocumentUri(
+                parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID));
     }
 
     /**
      * Delete the given document.
      *
      * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
+     * @return if the document was deleted successfully.
      */
     public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) {
         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
                 documentUri.getAuthority());
         try {
-            return deleteDocument(client, documentUri);
-        } finally {
-            ContentProviderClient.closeQuietly(client);
-        }
-    }
-
-    /** {@hide} */
-    public static boolean deleteDocument(ContentProviderClient client, Uri documentUri) {
-        final Bundle in = new Bundle();
-        in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri));
-
-        try {
-            final Bundle out = client.call(METHOD_DELETE_DOCUMENT, null, in);
+            deleteDocument(client, documentUri);
             return true;
         } catch (Exception e) {
             Log.w(TAG, "Failed to delete document", e);
             return false;
+        } finally {
+            ContentProviderClient.releaseQuietly(client);
         }
     }
+
+    /** {@hide} */
+    public static void deleteDocument(ContentProviderClient client, Uri documentUri)
+            throws RemoteException {
+        final Bundle in = new Bundle();
+        in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri));
+
+        client.call(METHOD_DELETE_DOCUMENT, null, in);
+    }
 }
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index e835a97..41d3700 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -423,9 +423,15 @@
                         }
                     } break;
                     case AccessibilityNodeInfo.FOCUS_INPUT: {
-                        // Input focus cannot go to virtual views.
                         View target = root.findFocus();
-                        if (target != null && isShown(target)) {
+                        if (target == null || !isShown(target)) {
+                            break;
+                        }
+                        AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
+                        if (provider != null) {
+                            focused = provider.findFocus(focusType);
+                        }
+                        if (focused == null) {
                             focused = target.createAccessibilityNodeInfo();
                         }
                     } break;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 48f0f9e..06f00f7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5249,6 +5249,7 @@
         info.setAccessibilityFocused(isAccessibilityFocused());
         info.setSelected(isSelected());
         info.setLongClickable(isLongClickable());
+        info.setLiveRegion(getAccessibilityLiveRegion());
 
         // TODO: These make sense only if we are in an AdapterView but all
         // views can be selected. Maybe from accessibility perspective
diff --git a/core/java/android/view/accessibility/AccessibilityNodeProvider.java b/core/java/android/view/accessibility/AccessibilityNodeProvider.java
index 688cbdf..718c32f 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeProvider.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeProvider.java
@@ -132,4 +132,19 @@
             int virtualViewId) {
         return null;
     }
+
+    /**
+     * Find the virtual view, i.e. a descendant of the host View, that has the
+     * specified focus type.
+     *
+     * @param focus The focus to find. One of
+     *            {@link AccessibilityNodeInfo#FOCUS_INPUT} or
+     *            {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}.
+     * @return The node info of the focused view or null.
+     * @see AccessibilityNodeInfo#FOCUS_INPUT
+     * @see AccessibilityNodeInfo#FOCUS_ACCESSIBILITY
+     */
+    public AccessibilityNodeInfo findFocus(int focus) {
+        return null;
+    }
 }
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 05fd613..cd853b6 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -100,8 +100,20 @@
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
-        onCreate(savedInstanceState, makeMyIntent(),
-                getResources().getText(com.android.internal.R.string.whichApplication),
+        // Use a specialized prompt when we're handling the 'Home' app startActivity()
+        final int titleResource;
+        final Intent intent = makeMyIntent();
+        final Set<String> categories = intent.getCategories();
+        if (Intent.ACTION_MAIN.equals(intent.getAction())
+                && categories != null
+                && categories.size() == 1
+                && categories.contains(Intent.CATEGORY_HOME)) {
+            titleResource = com.android.internal.R.string.whichHomeApplication;
+        } else {
+            titleResource = com.android.internal.R.string.whichApplication;
+        }
+
+        onCreate(savedInstanceState, intent, getResources().getText(titleResource),
                 null, null, true);
     }
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6c334e2..d57c232 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3182,6 +3182,8 @@
 
     <!-- Title of intent resolver dialog when selecting an application to run. -->
     <string name="whichApplication">Complete action using</string>
+    <!-- Title of intent resolver dialog when selecting a HOME application to run. -->
+    <string name="whichHomeApplication">Select a home app</string>
     <!-- Option to always use the selected application resolution in the future. See the "Complete action using" dialog title-->
     <string name="alwaysUse">Use by default for this action.</string>
     <!-- Text displayed when the user selects the check box for setting default application.  See the "Use by default for this action" check box. -->
@@ -4449,6 +4451,9 @@
     <!-- Print fail reason: unknown. [CHAR LIMIT=25] -->
     <string name="reason_unknown">unknown</string>
 
+    <!-- Print fail reason: the print service that has to process the print job is not available. [CHAR LIMIT=none] -->
+    <string name="reason_service_unavailable">Print service not enabled</string>
+
     <!-- Title for the notification that a print service was installed. [CHAR LIMIT=50] -->
     <string name="print_service_installed_title"><xliff:g id="name" example="Cloud Print">%s</xliff:g> service installed</string>
     <!-- Message for the notification that a print service was installed. [CHAR LIMIT=50] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c786888..3fdf6b9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -800,6 +800,7 @@
   <java-symbol type="string" name="web_user_agent_target_content" />
   <java-symbol type="string" name="webpage_unresponsive" />
   <java-symbol type="string" name="whichApplication" />
+  <java-symbol type="string" name="whichHomeApplication" />
   <java-symbol type="string" name="wifi_available_sign_in" />
   <java-symbol type="string" name="network_available_sign_in" />
   <java-symbol type="string" name="network_available_sign_in_detailed" />
@@ -927,6 +928,7 @@
   <java-symbol type="string" name="mediasize_japanese_kahu" />
   <java-symbol type="string" name="mediasize_japanese_kaku2" />
   <java-symbol type="string" name="mediasize_japanese_you4" />
+  <java-symbol type="string" name="reason_service_unavailable" />
   <java-symbol type="string" name="reason_unknown" />
   <java-symbol type="string" name="restr_pin_enter_admin_pin" />
   <java-symbol type="string" name="restr_pin_enter_pin" />
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 3425c91..84ea4c90 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -2687,7 +2687,11 @@
      */
     private void queueMsgUnderWakeLock(Handler handler, int msg,
             int arg1, int arg2, Object obj, int delay) {
+        final long ident = Binder.clearCallingIdentity();
+        // Always acquire the wake lock as AudioService because it is released by the
+        // message handler.
         mAudioEventWakeLock.acquire();
+        Binder.restoreCallingIdentity(ident);
         sendMsg(handler, msg, SENDMSG_QUEUE, arg1, arg2, obj, delay);
     }
 
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index def9aa7..0abd5f8 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -2162,6 +2162,7 @@
     private static final int MEDIA_STARTED = 6;
     private static final int MEDIA_PAUSED = 7;
     private static final int MEDIA_STOPPED = 8;
+    private static final int MEDIA_SKIPPED = 9;
     private static final int MEDIA_TIMED_TEXT = 99;
     private static final int MEDIA_ERROR = 100;
     private static final int MEDIA_INFO = 200;
@@ -2227,6 +2228,9 @@
               if (mOnSeekCompleteListener != null) {
                   mOnSeekCompleteListener.onSeekComplete(mMediaPlayer);
               }
+              // fall through
+
+            case MEDIA_SKIPPED:
               if (mTimeProvider != null) {
                   mTimeProvider.onSeekComplete(mMediaPlayer);
               }
@@ -2812,6 +2816,7 @@
         private Handler mEventHandler;
         private boolean mRefresh = false;
         private boolean mPausing = false;
+        private boolean mSeeking = false;
         private static final int NOTIFY = 1;
         private static final int NOTIFY_TIME = 0;
         private static final int REFRESH_AND_NOTIFY_TIME = 1;
@@ -2849,7 +2854,15 @@
         }
 
         private void scheduleNotification(int type, long delayUs) {
+            // ignore time notifications until seek is handled
+            if (mSeeking &&
+                    (type == NOTIFY_TIME || type == REFRESH_AND_NOTIFY_TIME)) {
+                return;
+            }
+
             if (DEBUG) Log.v(TAG, "scheduleNotification " + type + " in " + delayUs);
+            mStopped = type == NOTIFY_STOP;
+            mSeeking = type == NOTIFY_SEEK;
             mEventHandler.removeMessages(NOTIFY);
             Message msg = mEventHandler.obtainMessage(NOTIFY, type, 0);
             mEventHandler.sendMessageDelayed(msg, (int) (delayUs / 1000));
@@ -2876,7 +2889,6 @@
             synchronized(this) {
                 if (DEBUG) Log.d(TAG, "onPaused: " + paused);
                 if (mStopped) { // handle as seek if we were stopped
-                    mStopped = false;
                     scheduleNotification(NOTIFY_SEEK, 0 /* delay */);
                 } else {
                     mPausing = paused;  // special handling if player disappeared
@@ -2890,7 +2902,6 @@
             synchronized(this) {
                 if (DEBUG) Log.d(TAG, "onStopped");
                 mPaused = true;
-                mStopped = true;
                 scheduleNotification(NOTIFY_STOP, 0 /* delay */);
             }
         }
@@ -2899,7 +2910,6 @@
         @Override
         public void onSeekComplete(MediaPlayer mp) {
             synchronized(this) {
-                mStopped = false;
                 scheduleNotification(NOTIFY_SEEK, 0 /* delay */);
             }
         }
@@ -2914,6 +2924,7 @@
         }
 
         private synchronized void notifySeek() {
+            mSeeking = false;
             try {
                 long timeUs = getCurrentTimeUs(true, false);
                 if (DEBUG) Log.d(TAG, "onSeekComplete at " + timeUs);
@@ -3025,6 +3036,11 @@
             }
             long nextTimeUs = nowUs;
 
+            if (mSeeking) {
+                // skip timed-event notifications until seek is complete
+                return;
+            }
+
             if (DEBUG) {
                 StringBuilder sb = new StringBuilder();
                 sb.append("notifyTimedEvent(").append(mLastTimeUs).append(" -> ")
@@ -3124,6 +3140,11 @@
                     if (monotonic && mLastTimeUs < mLastReportedTime) {
                         /* have to adjust time */
                         mTimeAdjustment = mLastReportedTime - mLastTimeUs;
+                        if (mTimeAdjustment > 1000000) {
+                            // schedule seeked event if time jumped significantly
+                            // TODO: do this properly by introducing an exception
+                            scheduleNotification(NOTIFY_SEEK, 0 /* delay */);
+                        }
                     } else {
                         mTimeAdjustment = 0;
                     }
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index 71a0567..6faf7f8 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -2,6 +2,7 @@
         package="com.android.documentsui">
 
     <uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
+    <uses-permission android:name="android.permission.REMOVE_TASKS" />
 
     <application
         android:name=".DocumentsApplication"
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index b7dcb71..92c30ba 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -64,6 +64,8 @@
     <string name="save_error">Failed to save document</string>
     <!-- Toast shown when creating a folder failed with an error [CHAR LIMIT=48] -->
     <string name="create_error">Failed to create folder</string>
+    <!-- Error message shown when querying for a list of documents failed [CHAR LIMIT=48] -->
+    <string name="query_error">Failed to query documents</string>
 
     <!-- Title of storage root location that contains recently modified or used documents [CHAR LIMIT=24] -->
     <string name="root_recent">Recent</string>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
index 48bfaf0..23a3f22 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
@@ -16,10 +16,13 @@
 
 package com.android.documentsui;
 
+import static com.android.documentsui.DocumentsActivity.TAG;
+
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
 import android.app.FragmentManager;
+import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -29,6 +32,7 @@
 import android.os.Bundle;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.EditText;
@@ -36,8 +40,6 @@
 
 import com.android.documentsui.model.DocumentInfo;
 
-import java.io.FileNotFoundException;
-
 /**
  * Dialog to create a new directory.
  */
@@ -88,12 +90,19 @@
             final ContentResolver resolver = activity.getContentResolver();
 
             final DocumentInfo cwd = activity.getCurrentDirectory();
-            final Uri childUri = DocumentsContract.createDocument(
-                    resolver, cwd.derivedUri, Document.MIME_TYPE_DIR, mDisplayName);
+
+            ContentProviderClient client = null;
             try {
+                client = DocumentsApplication.acquireUnstableProviderOrThrow(
+                        resolver, cwd.derivedUri.getAuthority());
+                final Uri childUri = DocumentsContract.createDocument(
+                        client, cwd.derivedUri, Document.MIME_TYPE_DIR, mDisplayName);
                 return DocumentInfo.fromUri(resolver, childUri);
-            } catch (FileNotFoundException e) {
+            } catch (Exception e) {
+                Log.w(TAG, "Failed to create directory", e);
                 return null;
+            } finally {
+                ContentProviderClient.releaseQuietly(client);
             }
         }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 1f11aed..6ff47f8 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -31,6 +31,7 @@
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
 import android.app.LoaderManager.LoaderCallbacks;
+import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -259,7 +260,7 @@
             public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
                 if (!isAdded()) return;
 
-                mAdapter.swapCursor(result.cursor);
+                mAdapter.swapResult(result.cursor, result.exception);
 
                 // Push latest state up to UI
                 // TODO: if mode change was racing with us, don't overwrite it
@@ -285,7 +286,7 @@
 
             @Override
             public void onLoaderReset(Loader<DirectoryResult> loader) {
-                mAdapter.swapCursor(null);
+                mAdapter.swapResult(null, null);
             }
         };
 
@@ -552,9 +553,16 @@
                 continue;
             }
 
-            if (!DocumentsContract.deleteDocument(resolver, doc.derivedUri)) {
+            ContentProviderClient client = null;
+            try {
+                client = DocumentsApplication.acquireUnstableProviderOrThrow(
+                        resolver, doc.derivedUri.getAuthority());
+                DocumentsContract.deleteDocument(client, doc.derivedUri);
+            } catch (Exception e) {
                 Log.w(TAG, "Failed to delete " + doc);
                 hadTrouble = true;
+            } finally {
+                ContentProviderClient.releaseQuietly(client);
             }
         }
 
@@ -646,7 +654,7 @@
 
         private List<Footer> mFooters = Lists.newArrayList();
 
-        public void swapCursor(Cursor cursor) {
+        public void swapResult(Cursor cursor, Exception e) {
             mCursor = cursor;
             mCursorCount = cursor != null ? cursor.getCount() : 0;
 
@@ -667,6 +675,11 @@
                 }
             }
 
+            if (e != null) {
+                mFooters.add(new MessageFooter(
+                        3, R.drawable.ic_dialog_alert, getString(R.string.query_error)));
+            }
+
             if (isEmpty()) {
                 mEmptyView.setVisibility(View.VISIBLE);
             } else {
@@ -971,19 +984,23 @@
         @Override
         protected Bitmap doInBackground(Uri... params) {
             final Context context = mIconThumb.getContext();
+            final ContentResolver resolver = context.getContentResolver();
 
+            ContentProviderClient client = null;
             Bitmap result = null;
             try {
-                // TODO: switch to using unstable provider
-                result = DocumentsContract.getDocumentThumbnail(
-                        context.getContentResolver(), mUri, mThumbSize, mSignal);
+                client = DocumentsApplication.acquireUnstableProviderOrThrow(
+                        resolver, mUri.getAuthority());
+                result = DocumentsContract.getDocumentThumbnail(client, mUri, mThumbSize, mSignal);
                 if (result != null) {
                     final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
                             context, mThumbSize);
                     thumbs.put(mUri, result);
                 }
             } catch (Exception e) {
-                Log.w(TAG, "Failed to load thumbnail: " + e);
+                Log.w(TAG, "Failed to load thumbnail for " + mUri + ": " + e);
+            } finally {
+                ContentProviderClient.releaseQuietly(client);
             }
             return result;
         }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
index 0b3ecf8..da0f526 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
@@ -56,7 +56,7 @@
     @Override
     public void close() {
         IoUtils.closeQuietly(cursor);
-        ContentProviderClient.closeQuietly(client);
+        ContentProviderClient.releaseQuietly(client);
         cursor = null;
         client = null;
     }
@@ -158,7 +158,9 @@
                 + result.mode + ", sortOrder=" + result.sortOrder);
 
         try {
-            result.client = resolver.acquireUnstableContentProviderClient(authority);
+            result.client = DocumentsApplication.acquireUnstableProviderOrThrow(
+                    resolver, authority);
+
             cursor = result.client.query(
                     mUri, null, null, null, getQuerySortOrder(result.sortOrder), mSignal);
             cursor.registerContentObserver(mObserver);
@@ -177,7 +179,7 @@
         } catch (Exception e) {
             Log.w(TAG, "Failed to query", e);
             result.exception = e;
-            ContentProviderClient.closeQuietly(result.client);
+            ContentProviderClient.releaseQuietly(result.client);
         } finally {
             synchronized (this) {
                 mSignal = null;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 4caec8f..7a45641 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -35,6 +35,7 @@
 import android.content.ActivityNotFoundException;
 import android.content.ClipData;
 import android.content.ComponentName;
+import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Intent;
@@ -878,6 +879,7 @@
                         mRoot.authority, mRoot.documentId);
                 return DocumentInfo.fromUri(getContentResolver(), uri);
             } catch (FileNotFoundException e) {
+                Log.w(TAG, "Failed to find root", e);
                 return null;
             }
         }
@@ -1035,12 +1037,26 @@
 
         @Override
         protected Uri doInBackground(Void... params) {
+            final ContentResolver resolver = getContentResolver();
             final DocumentInfo cwd = getCurrentDirectory();
-            final Uri childUri = DocumentsContract.createDocument(
-                    getContentResolver(), cwd.derivedUri, mMimeType, mDisplayName);
+
+            ContentProviderClient client = null;
+            Uri childUri = null;
+            try {
+                client = DocumentsApplication.acquireUnstableProviderOrThrow(
+                        resolver, cwd.derivedUri.getAuthority());
+                childUri = DocumentsContract.createDocument(
+                        client, cwd.derivedUri, mMimeType, mDisplayName);
+            } catch (Exception e) {
+                Log.w(TAG, "Failed to create document", e);
+            } finally {
+                ContentProviderClient.releaseQuietly(client);
+            }
+
             if (childUri != null) {
                 saveStackBlocking();
             }
+
             return childUri;
         }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java
index 960181a..6b46e3a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java
@@ -19,13 +19,19 @@
 import android.app.ActivityManager;
 import android.app.Application;
 import android.content.BroadcastReceiver;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.graphics.Point;
 import android.net.Uri;
+import android.os.RemoteException;
+import android.text.format.DateUtils;
 
 public class DocumentsApplication extends Application {
+    private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS;
+
     private RootsCache mRoots;
     private Point mThumbnailsSize;
     private ThumbnailCache mThumbnails;
@@ -44,6 +50,17 @@
         return thumbnails;
     }
 
+    public static ContentProviderClient acquireUnstableProviderOrThrow(
+            ContentResolver resolver, String authority) throws RemoteException {
+        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
+                authority);
+        if (client == null) {
+            throw new RemoteException("Failed to acquire provider for " + authority);
+        }
+        client.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
+        return client;
+    }
+
     @Override
     public void onCreate() {
         final ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
index 9a4fb7d..47dbcdf 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
@@ -19,11 +19,12 @@
 import static com.android.documentsui.DocumentsActivity.TAG;
 import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED;
 
+import android.app.ActivityManager;
 import android.content.AsyncTaskLoader;
 import android.content.ContentProviderClient;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
+import android.database.MatrixCursor;
 import android.database.MergeCursor;
 import android.net.Uri;
 import android.os.Bundle;
@@ -56,9 +57,8 @@
 public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
     private static final boolean LOGD = true;
 
-    // TODO: adjust for svelte devices
-    // TODO: add support for oneway queries to avoid wedging loader
-    private static final int MAX_OUTSTANDING_RECENTS = 2;
+    private static final int MAX_OUTSTANDING_RECENTS = 4;
+    private static final int MAX_OUTSTANDING_RECENTS_SVELTE = 2;
 
     /**
      * Time to wait for first pass to complete before returning partial results.
@@ -74,20 +74,29 @@
     /** MIME types that should always be excluded from recents. */
     private static final String[] RECENT_REJECT_MIMES = new String[] { Document.MIME_TYPE_DIR };
 
-    private static final ExecutorService sExecutor = buildExecutor();
+    private static ExecutorService sExecutor;
 
     /**
      * Create a bounded thread pool for fetching recents; it creates threads as
      * needed (up to maximum) and reclaims them when finished.
      */
-    private static ExecutorService buildExecutor() {
-        // Create a bounded thread pool for fetching recents; it creates
-        // threads as needed (up to maximum) and reclaims them when finished.
-        final ThreadPoolExecutor executor = new ThreadPoolExecutor(
-                MAX_OUTSTANDING_RECENTS, MAX_OUTSTANDING_RECENTS, 10, TimeUnit.SECONDS,
-                new LinkedBlockingQueue<Runnable>());
-        executor.allowCoreThreadTimeOut(true);
-        return executor;
+    private synchronized static ExecutorService getExecutor(Context context) {
+        if (sExecutor == null) {
+            final ActivityManager am = (ActivityManager) context.getSystemService(
+                    Context.ACTIVITY_SERVICE);
+            final int maxOutstanding = am.isLowRamDevice() ? MAX_OUTSTANDING_RECENTS_SVELTE
+                    : MAX_OUTSTANDING_RECENTS;
+
+            // Create a bounded thread pool for fetching recents; it creates
+            // threads as needed (up to maximum) and reclaims them when finished.
+            final ThreadPoolExecutor executor = new ThreadPoolExecutor(
+                    maxOutstanding, maxOutstanding, 10, TimeUnit.SECONDS,
+                    new LinkedBlockingQueue<Runnable>());
+            executor.allowCoreThreadTimeOut(true);
+            sExecutor = executor;
+        }
+
+        return sExecutor;
     }
 
     private final RootsCache mRoots;
@@ -120,25 +129,26 @@
         public void run() {
             if (isCancelled()) return;
 
-            final ContentResolver resolver = getContext().getContentResolver();
-            final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
-                    authority);
+            ContentProviderClient client = null;
             try {
+                client = DocumentsApplication.acquireUnstableProviderOrThrow(
+                        getContext().getContentResolver(), authority);
+
                 final Uri uri = DocumentsContract.buildRecentDocumentsUri(authority, rootId);
                 final Cursor cursor = client.query(
                         uri, null, null, null, DirectoryLoader.getQuerySortOrder(mSortOrder));
                 mWithRoot = new RootCursorWrapper(authority, rootId, cursor, MAX_DOCS_FROM_ROOT);
-                set(mWithRoot);
-
-                mFirstPassLatch.countDown();
-                if (mFirstPassDone) {
-                    onContentChanged();
-                }
-
             } catch (Exception e) {
-                setException(e);
+                Log.w(TAG, "Failed to load " + authority + ", " + rootId, e);
             } finally {
-                ContentProviderClient.closeQuietly(client);
+                ContentProviderClient.releaseQuietly(client);
+            }
+
+            set(mWithRoot);
+
+            mFirstPassLatch.countDown();
+            if (mFirstPassDone) {
+                onContentChanged();
             }
         }
 
@@ -156,6 +166,8 @@
 
     @Override
     public DirectoryResult loadInBackground() {
+        final ExecutorService executor = getExecutor(getContext());
+
         if (mFirstPassLatch == null) {
             // First time through we kick off all the recent tasks, and wait
             // around to see if everyone finishes quickly.
@@ -170,7 +182,7 @@
 
             mFirstPassLatch = new CountDownLatch(mTasks.size());
             for (RecentTask task : mTasks.values()) {
-                sExecutor.execute(task);
+                executor.execute(task);
             }
 
             try {
@@ -184,11 +196,14 @@
         final long rejectBefore = System.currentTimeMillis() - REJECT_OLDER_THAN;
 
         // Collect all finished tasks
+        boolean allDone = true;
         List<Cursor> cursors = Lists.newArrayList();
         for (RecentTask task : mTasks.values()) {
             if (task.isDone()) {
                 try {
                     final Cursor cursor = task.get();
+                    if (cursor == null) continue;
+
                     final FilteringCursorWrapper filtered = new FilteringCursorWrapper(
                             cursor, mState.acceptMimes, RECENT_REJECT_MIMES, rejectBefore) {
                         @Override
@@ -200,14 +215,16 @@
                 } catch (InterruptedException e) {
                     throw new RuntimeException(e);
                 } catch (ExecutionException e) {
-                    Log.w(TAG, "Failed to load " + task.authority + ", " + task.rootId, e);
+                    // We already logged on other side
                 }
+            } else {
+                allDone = false;
             }
         }
 
         if (LOGD) {
             Log.d(TAG, "Found " + cursors.size() + " of " + mTasks.size() + " recent queries done");
-            Log.d(TAG, sExecutor.toString());
+            Log.d(TAG, executor.toString());
         }
 
         final DirectoryResult result = new DirectoryResult();
@@ -215,11 +232,18 @@
 
         // Hint to UI if we're still loading
         final Bundle extras = new Bundle();
-        if (cursors.size() != mTasks.size()) {
+        if (!allDone) {
             extras.putBoolean(DocumentsContract.EXTRA_LOADING, true);
         }
 
-        final MergeCursor merged = new MergeCursor(cursors.toArray(new Cursor[cursors.size()]));
+        final Cursor merged;
+        if (cursors.size() > 0) {
+            merged = new MergeCursor(cursors.toArray(new Cursor[cursors.size()]));
+        } else {
+            // Return something when nobody is ready
+            merged = new MatrixCursor(new String[0]);
+        }
+
         final SortingCursorWrapper sorted = new SortingCursorWrapper(merged, result.sortOrder) {
             @Override
             public Bundle getExtras() {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index e3908e9..bad0a96 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -243,10 +243,11 @@
 
         final List<RootInfo> roots = Lists.newArrayList();
         final Uri rootsUri = DocumentsContract.buildRootsUri(authority);
-        final ContentProviderClient client = resolver
-                .acquireUnstableContentProviderClient(authority);
+
+        ContentProviderClient client = null;
         Cursor cursor = null;
         try {
+            client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority);
             cursor = client.query(rootsUri, null, null, null, null);
             while (cursor.moveToNext()) {
                 final RootInfo root = RootInfo.fromRootsCursor(authority, cursor);
@@ -256,7 +257,7 @@
             Log.w(TAG, "Failed to load some roots from " + authority + ": " + e);
         } finally {
             IoUtils.closeQuietly(cursor);
-            ContentProviderClient.closeQuietly(client);
+            ContentProviderClient.releaseQuietly(client);
         }
         return roots;
     }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
index 5091a61..91d9124 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
@@ -23,9 +23,10 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.provider.DocumentsContract;
-import android.provider.DocumentsProvider;
 import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsProvider;
 
+import com.android.documentsui.DocumentsApplication;
 import com.android.documentsui.RootCursorWrapper;
 
 import libcore.io.IoUtils;
@@ -178,10 +179,11 @@
     }
 
     public void updateFromUri(ContentResolver resolver, Uri uri) throws FileNotFoundException {
-        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
-                uri.getAuthority());
+        ContentProviderClient client = null;
         Cursor cursor = null;
         try {
+            client = DocumentsApplication.acquireUnstableProviderOrThrow(
+                    resolver, uri.getAuthority());
             cursor = client.query(uri, null, null, null, null);
             if (!cursor.moveToFirst()) {
                 throw new FileNotFoundException("Missing details for " + uri);
@@ -191,7 +193,7 @@
             throw asFileNotFoundException(t);
         } finally {
             IoUtils.closeQuietly(cursor);
-            ContentProviderClient.closeQuietly(client);
+            ContentProviderClient.releaseQuietly(client);
         }
     }
 
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java
index 5a15cd2..e9f2c71 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java
@@ -54,11 +54,20 @@
 public class TestDocumentsProvider extends DocumentsProvider {
     private static final String TAG = "TestDocuments";
 
-    private static final boolean LAG_ROOTS = false;
-    private static final boolean CRASH_ROOTS = false;
-    private static final boolean REFRESH_ROOTS = false;
+    private static final boolean ROOTS_WEDGE = false;
+    private static final boolean ROOTS_LAG = false;
+    private static final boolean ROOTS_CRASH = false;
+    private static final boolean ROOTS_REFRESH = false;
 
-    private static final boolean CRASH_DOCUMENT = false;
+    private static final boolean DOCUMENT_CRASH = false;
+
+    private static final boolean RECENT_WEDGE = false;
+
+    private static final boolean CHILD_WEDGE = false;
+    private static final boolean CHILD_CRASH = false;
+
+    private static final boolean THUMB_WEDGE = false;
+    private static final boolean THUMB_CRASH = false;
 
     private static final String MY_ROOT_ID = "myRoot";
     private static final String MY_DOC_ID = "myDoc";
@@ -95,10 +104,11 @@
     public Cursor queryRoots(String[] projection) throws FileNotFoundException {
         Log.d(TAG, "Someone asked for our roots!");
 
-        if (LAG_ROOTS) SystemClock.sleep(3000);
-        if (CRASH_ROOTS) System.exit(12);
+        if (ROOTS_WEDGE) SystemClock.sleep(Integer.MAX_VALUE);
+        if (ROOTS_LAG) SystemClock.sleep(3000);
+        if (ROOTS_CRASH) System.exit(12);
 
-        if (REFRESH_ROOTS) {
+        if (ROOTS_REFRESH) {
             new AsyncTask<Void, Void, Void>() {
                 @Override
                 protected Void doInBackground(Void... params) {
@@ -126,7 +136,7 @@
     @Override
     public Cursor queryDocument(String documentId, String[] projection)
             throws FileNotFoundException {
-        if (CRASH_DOCUMENT) System.exit(12);
+        if (DOCUMENT_CRASH) System.exit(12);
 
         final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
         includeFile(result, documentId, 0);
@@ -198,6 +208,9 @@
             String parentDocumentId, String[] projection, String sortOrder)
             throws FileNotFoundException {
 
+        if (CHILD_WEDGE) SystemClock.sleep(Integer.MAX_VALUE);
+        if (CHILD_CRASH) System.exit(12);
+
         final ContentResolver resolver = getContext().getContentResolver();
         final Uri notifyUri = DocumentsContract.buildDocumentUri(
                 "com.example.documents", parentDocumentId);
@@ -257,6 +270,9 @@
     @Override
     public Cursor queryRecentDocuments(String rootId, String[] projection)
             throws FileNotFoundException {
+
+        if (RECENT_WEDGE) SystemClock.sleep(Integer.MAX_VALUE);
+
         // Pretend to take a super long time to respond
         SystemClock.sleep(3000);
 
@@ -275,6 +291,10 @@
     @Override
     public AssetFileDescriptor openDocumentThumbnail(
             String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
+
+        if (THUMB_WEDGE) SystemClock.sleep(Integer.MAX_VALUE);
+        if (THUMB_CRASH) System.exit(12);
+
         final Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
         final Canvas canvas = new Canvas(bitmap);
         final Paint paint = new Paint();
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index ddb6d1a..c1ac7d5 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -235,7 +235,7 @@
     static final boolean DEBUG_USER_LEAVING = localLOGV || false;
     static final boolean DEBUG_VISBILITY = localLOGV || false;
     static final boolean DEBUG_PSS = localLOGV || false;
-    static final boolean DEBUG_LOCKSCREEN = localLOGV || true;
+    static final boolean DEBUG_LOCKSCREEN = localLOGV || false;
     static final boolean VALIDATE_TOKENS = true;
     static final boolean SHOW_ACTIVITY_START_TIME = true;
 
@@ -7823,6 +7823,31 @@
         }
     }
 
+    @Override
+    public void appNotRespondingViaProvider(IBinder connection) {
+        enforceCallingPermission(
+                android.Manifest.permission.REMOVE_TASKS, "appNotRespondingViaProvider()");
+
+        final ContentProviderConnection conn = (ContentProviderConnection) connection;
+        if (conn == null) {
+            Slog.w(TAG, "ContentProviderConnection is null");
+            return;
+        }
+
+        final ProcessRecord host = conn.provider.proc;
+        if (host == null) {
+            Slog.w(TAG, "Failed to find hosting ProcessRecord");
+            return;
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            appNotResponding(host, null, null, false, "ContentProvider not responding");
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     public static final void installSystemProviders() {
         List<ProviderInfo> providers;
         synchronized (mSelf) {
diff --git a/services/java/com/android/server/print/UserState.java b/services/java/com/android/server/print/UserState.java
index bc70fe3..3b0ee24 100644
--- a/services/java/com/android/server/print/UserState.java
+++ b/services/java/com/android/server/print/UserState.java
@@ -134,6 +134,11 @@
         }
         if (service != null) {
             service.onPrintJobQueued(printJob);
+        } else {
+            // The service for the job is no longer enabled, so just
+            // fail the job with the appropriate message.
+            mSpooler.setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
+                    mContext.getString(R.string.reason_service_unavailable));
         }
     }
 
@@ -779,7 +784,7 @@
             for (int i = 0; i < printJobCount; i++) {
                 PrintJobInfo printJob = printJobs.get(i);
                 mSpooler.setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
-                        mContext.getString(R.string.reason_unknown));
+                        mContext.getString(R.string.reason_service_unavailable));
             }
         } finally {
             Binder.restoreCallingIdentity(identity);
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 80c50cc..e6b0531 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -255,6 +255,9 @@
     /** Amount of time (in milliseconds) to delay before declaring a window freeze timeout. */
     static final int WINDOW_FREEZE_TIMEOUT_DURATION = 2000;
 
+    /** Amount of time (in milliseconds) to delay before declaring a starting window leaked. */
+    static final int STARTING_WINDOW_TIMEOUT_DURATION = 10000;
+
     /**
      * If true, the window manager will do its own custom freezing and general
      * management of the screen during rotation.
@@ -2256,6 +2259,8 @@
                 token.appWindowToken.startingWindow = win;
                 if (DEBUG_STARTING_WINDOW) Slog.v (TAG, "addWindow: " + token.appWindowToken
                         + " startingWindow=" + win);
+                Message m = mH.obtainMessage(H.REMOVE_STARTING_TIMEOUT, token.appWindowToken);
+                mH.sendMessageDelayed(m, STARTING_WINDOW_TIMEOUT_DURATION);
             }
 
             boolean imMayMove = true;
@@ -2356,6 +2361,10 @@
     }
 
     public void removeWindowLocked(Session session, WindowState win) {
+        if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
+            if (DEBUG_STARTING_WINDOW) Slog.d(TAG, "Starting window removed " + win);
+            removeStartingWindowTimeout(win.mAppToken);
+        }
 
         if (localLOGV || DEBUG_FOCUS || DEBUG_FOCUS_LIGHT && win==mCurrentFocus) Slog.v(
             TAG, "Remove " + win + " client="
@@ -2498,6 +2507,7 @@
         if (atoken != null) {
             if (atoken.startingWindow == win) {
                 if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Nulling startingWindow " + win);
+                removeStartingWindowTimeout(atoken);
                 atoken.startingWindow = null;
             } else if (atoken.allAppWindows.size() == 0 && atoken.startingData != null) {
                 // If this is the last window and we had requested a starting
@@ -2507,12 +2517,7 @@
             } else if (atoken.allAppWindows.size() == 1 && atoken.startingView != null) {
                 // If this is the last window except for a starting transition
                 // window, we need to get rid of the starting transition.
-                if (DEBUG_STARTING_WINDOW) {
-                    Slog.v(TAG, "Schedule remove starting " + token
-                            + ": no more real windows");
-                }
-                Message m = mH.obtainMessage(H.REMOVE_STARTING, atoken);
-                mH.sendMessage(m);
+                scheduleRemoveStartingWindow(atoken);
             }
         }
 
@@ -3964,6 +3969,7 @@
                         if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) {
                             Slog.v(TAG, "Removing starting window: " + startingWindow);
                         }
+                        removeStartingWindowTimeout(ttoken);
                         startingWindow.getWindowList().remove(startingWindow);
                         mWindowsChanged = true;
                         if (DEBUG_ADD_REMOVE) Slog.v(TAG,
@@ -4527,14 +4533,29 @@
         }
         Binder.restoreCallingIdentity(origId);
 
-        if (startingToken != null) {
-            if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Schedule remove starting "
-                    + startingToken + ": app token removed");
-            Message m = mH.obtainMessage(H.REMOVE_STARTING, startingToken);
-            mH.sendMessage(m);
+        // Will only remove if startingToken non null.
+        scheduleRemoveStartingWindow(startingToken);
+    }
+
+    void removeStartingWindowTimeout(AppWindowToken wtoken) {
+        if (wtoken != null) {
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG, Debug.getCallers(1) +
+                    ": Remove starting window timeout " + wtoken + (wtoken != null ?
+                    " startingWindow=" + wtoken.startingWindow : ""));
+            mH.removeMessages(H.REMOVE_STARTING_TIMEOUT, wtoken);
         }
     }
 
+    void scheduleRemoveStartingWindow(AppWindowToken wtoken) {
+        if (wtoken != null && wtoken.startingWindow != null) {
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG, Debug.getCallers(1) +
+                    ": Schedule remove starting " + wtoken + (wtoken != null ?
+                    " startingWindow=" + wtoken.startingWindow : ""));
+            removeStartingWindowTimeout(wtoken);
+            Message m = mH.obtainMessage(H.REMOVE_STARTING, wtoken);
+            mH.sendMessage(m);
+        }
+    }
     private boolean tmpRemoveAppWindowsLocked(WindowToken token) {
         final int NW = token.windows.size();
         if (NW > 0) {
@@ -7053,6 +7074,8 @@
         public static final int TAP_OUTSIDE_STACK = 31;
         public static final int NOTIFY_ACTIVITY_DRAWN = 32;
 
+        public static final int REMOVE_STARTING_TIMEOUT = 33;
+
         @Override
         public void handleMessage(Message msg) {
             if (DEBUG_WINDOW_TRACE) {
@@ -7151,6 +7174,7 @@
                                             "Aborted starting " + wtoken
                                             + ": removed=" + wtoken.removed
                                             + " startingData=" + wtoken.startingData);
+                                    removeStartingWindowTimeout(wtoken);
                                     wtoken.startingWindow = null;
                                     wtoken.startingData = null;
                                     abort = true;
@@ -7175,6 +7199,11 @@
                     }
                 } break;
 
+                case REMOVE_STARTING_TIMEOUT: {
+                    final AppWindowToken wtoken = (AppWindowToken)msg.obj;
+                    Slog.e(TAG, "Starting window " + wtoken + " timed out");
+                    // Fall through.
+                }
                 case REMOVE_STARTING: {
                     final AppWindowToken wtoken = (AppWindowToken)msg.obj;
                     IBinder token = null;
@@ -9676,6 +9705,7 @@
                     winAnimator.mSurfaceShown = false;
                     winAnimator.mSurfaceControl = null;
                     winAnimator.mWin.mHasSurface = false;
+                    scheduleRemoveStartingWindow(winAnimator.mWin.mAppToken);
                 }
 
                 try {