Add facility for broadcasts receives to do work asynchronously.

You can now call goAsync() and move your work to a background thread.
If you are that kind of receiver.  You weirdo.

Also allows SharedPreferences.apply() to be committed off the main
thread after returning from onReceive().

Change-Id: I27f975910e28f230ababcaeb551eb9a78ec4fc76
diff --git a/api/current.xml b/api/current.xml
index 5ed07cf..841daa1 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -40186,6 +40186,17 @@
 <parameter name="makeMap" type="boolean">
 </parameter>
 </method>
+<method name="goAsync"
+ return="android.content.BroadcastReceiver.PendingResult"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="isInitialStickyBroadcast"
  return="boolean"
  abstract="false"
@@ -40321,6 +40332,150 @@
 </parameter>
 </method>
 </class>
+<class name="BroadcastReceiver.PendingResult"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="abortBroadcast"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="clearAbortBroadcast"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="finish"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getAbortBroadcast"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getResultCode"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getResultData"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getResultExtras"
+ return="android.os.Bundle"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="makeMap" type="boolean">
+</parameter>
+</method>
+<method name="setResult"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="code" type="int">
+</parameter>
+<parameter name="data" type="java.lang.String">
+</parameter>
+<parameter name="extras" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="setResultCode"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="code" type="int">
+</parameter>
+</method>
+<method name="setResultData"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="data" type="java.lang.String">
+</parameter>
+</method>
+<method name="setResultExtras"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="extras" type="android.os.Bundle">
+</parameter>
+</method>
+</class>
 <class name="ClipData"
  extends="java.lang.Object"
  abstract="false"
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index c5badaf..754295a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -117,12 +117,14 @@
  * {@hide}
  */
 public final class ActivityThread {
-    static final String TAG = "ActivityThread";
+    /** @hide */
+    public static final String TAG = "ActivityThread";
     private static final android.graphics.Bitmap.Config THUMBNAIL_FORMAT = Bitmap.Config.RGB_565;
     private static final boolean DEBUG = false;
     static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
     static final boolean DEBUG_MESSAGES = false;
-    static final boolean DEBUG_BROADCAST = false;
+    /** @hide */
+    public static final boolean DEBUG_BROADCAST = false;
     private static final boolean DEBUG_RESULTS = false;
     private static final boolean DEBUG_BACKUP = false;
     private static final boolean DEBUG_CONFIGURATION = false;
@@ -262,18 +264,20 @@
         }
     }
 
-    private static final class ReceiverData {
+    private static final class ReceiverData extends BroadcastReceiver.PendingResult {
+        public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras,
+                boolean ordered, boolean sticky, IBinder token) {
+            super(resultCode, resultData, resultExtras, TYPE_COMPONENT, ordered, sticky, token);
+            this.intent = intent;
+        }
+        
         Intent intent;
         ActivityInfo info;
-        int resultCode;
-        String resultData;
-        Bundle resultExtras;
-        boolean sync;
-        boolean resultAbort;
         public String toString() {
             return "ReceiverData{intent=" + intent + " packageName=" +
-            info.packageName + " resultCode=" + resultCode
-            + " resultData=" + resultData + " resultExtras=" + resultExtras + "}";
+                    info.packageName + " resultCode=" + getResultCode()
+                    + " resultData=" + getResultData() + " resultExtras="
+                    + getResultExtras(false) + "}";
         }
     }
 
@@ -466,15 +470,9 @@
 
         public final void scheduleReceiver(Intent intent, ActivityInfo info,
                 int resultCode, String data, Bundle extras, boolean sync) {
-            ReceiverData r = new ReceiverData();
-
-            r.intent = intent;
+            ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
+                    sync, false, mAppThread.asBinder());
             r.info = info;
-            r.resultCode = resultCode;
-            r.resultData = data;
-            r.resultExtras = extras;
-            r.sync = sync;
-
             queueOrSendMessage(H.RECEIVER, r);
         }
 
@@ -1799,18 +1797,12 @@
         try {
             java.lang.ClassLoader cl = packageInfo.getClassLoader();
             data.intent.setExtrasClassLoader(cl);
-            if (data.resultExtras != null) {
-                data.resultExtras.setClassLoader(cl);
-            }
+            data.setExtrasClassLoader(cl);
             receiver = (BroadcastReceiver)cl.loadClass(component).newInstance();
         } catch (Exception e) {
-            try {
-                if (DEBUG_BROADCAST) Slog.i(TAG,
-                        "Finishing failed broadcast to " + data.intent.getComponent());
-                mgr.finishReceiver(mAppThread.asBinder(), data.resultCode,
-                                   data.resultData, data.resultExtras, data.resultAbort);
-            } catch (RemoteException ex) {
-            }
+            if (DEBUG_BROADCAST) Slog.i(TAG,
+                    "Finishing failed broadcast to " + data.intent.getComponent());
+            data.sendFinished(mgr);
             throw new RuntimeException(
                 "Unable to instantiate receiver " + component
                 + ": " + e.toString(), e);
@@ -1828,20 +1820,13 @@
                 + ", dir=" + packageInfo.getAppDir());
 
             ContextImpl context = (ContextImpl)app.getBaseContext();
-            receiver.setOrderedHint(true);
-            receiver.setResult(data.resultCode, data.resultData,
-                data.resultExtras);
-            receiver.setOrderedHint(data.sync);
+            receiver.setPendingResult(data);
             receiver.onReceive(context.getReceiverRestrictedContext(),
                     data.intent);
         } catch (Exception e) {
-            try {
-                if (DEBUG_BROADCAST) Slog.i(TAG,
-                        "Finishing failed broadcast to " + data.intent.getComponent());
-                mgr.finishReceiver(mAppThread.asBinder(), data.resultCode,
-                    data.resultData, data.resultExtras, data.resultAbort);
-            } catch (RemoteException ex) {
-            }
+            if (DEBUG_BROADCAST) Slog.i(TAG,
+                    "Finishing failed broadcast to " + data.intent.getComponent());
+            data.sendFinished(mgr);
             if (!mInstrumentation.onException(receiver, e)) {
                 throw new RuntimeException(
                     "Unable to start receiver " + component
@@ -1849,22 +1834,8 @@
             }
         }
 
-        QueuedWork.waitToFinish();
-
-        try {
-            if (data.sync) {
-                if (DEBUG_BROADCAST) Slog.i(TAG,
-                        "Finishing ordered broadcast to " + data.intent.getComponent());
-                mgr.finishReceiver(
-                    mAppThread.asBinder(), receiver.getResultCode(),
-                    receiver.getResultData(), receiver.getResultExtras(false),
-                        receiver.getAbortBroadcast());
-            } else {
-                if (DEBUG_BROADCAST) Slog.i(TAG,
-                        "Finishing broadcast to " + data.intent.getComponent());
-                mgr.finishReceiver(mAppThread.asBinder(), 0, null, null, false);
-            }
-        } catch (RemoteException ex) {
+        if (receiver.getPendingResult() != null) {
+            data.finish();
         }
     }
 
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 0644f96..7f24d27 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -660,37 +660,40 @@
         final IntentReceiverLeaked mLocation;
         RuntimeException mUnregisterLocation;
 
-        final class Args implements Runnable {
+        final class Args extends BroadcastReceiver.PendingResult implements Runnable {
             private Intent mCurIntent;
-            private int mCurCode;
-            private String mCurData;
-            private Bundle mCurMap;
-            private boolean mCurOrdered;
-            private boolean mCurSticky;
+            private final boolean mOrdered;
 
+            public Args(Intent intent, int resultCode, String resultData, Bundle resultExtras,
+                    boolean ordered, boolean sticky) {
+                super(resultCode, resultData, resultExtras,
+                        mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED,
+                        ordered, sticky, mIIntentReceiver.asBinder());
+                mCurIntent = intent;
+                mOrdered = ordered;
+            }
+            
             public void run() {
-                BroadcastReceiver receiver = mReceiver;
+                final BroadcastReceiver receiver = mReceiver;
+                final boolean ordered = mOrdered;
+                
                 if (ActivityThread.DEBUG_BROADCAST) {
                     int seq = mCurIntent.getIntExtra("seq", -1);
                     Slog.i(ActivityThread.TAG, "Dispatching broadcast " + mCurIntent.getAction()
                             + " seq=" + seq + " to " + mReceiver);
                     Slog.i(ActivityThread.TAG, "  mRegistered=" + mRegistered
-                            + " mCurOrdered=" + mCurOrdered);
+                            + " mOrderedHint=" + ordered);
                 }
                 
-                IActivityManager mgr = ActivityManagerNative.getDefault();
-                Intent intent = mCurIntent;
+                final IActivityManager mgr = ActivityManagerNative.getDefault();
+                final Intent intent = mCurIntent;
                 mCurIntent = null;
                 
                 if (receiver == null) {
-                    if (mRegistered && mCurOrdered) {
-                        try {
-                            if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
-                                    "Finishing null broadcast to " + mReceiver);
-                            mgr.finishReceiver(mIIntentReceiver,
-                                    mCurCode, mCurData, mCurMap, false);
-                        } catch (RemoteException ex) {
-                        }
+                    if (mRegistered && ordered) {
+                        if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+                                "Finishing null broadcast to " + mReceiver);
+                        sendFinished(mgr);
                     }
                     return;
                 }
@@ -698,24 +701,14 @@
                 try {
                     ClassLoader cl =  mReceiver.getClass().getClassLoader();
                     intent.setExtrasClassLoader(cl);
-                    if (mCurMap != null) {
-                        mCurMap.setClassLoader(cl);
-                    }
-                    receiver.setOrderedHint(true);
-                    receiver.setResult(mCurCode, mCurData, mCurMap);
-                    receiver.clearAbortBroadcast();
-                    receiver.setOrderedHint(mCurOrdered);
-                    receiver.setInitialStickyHint(mCurSticky);
+                    setExtrasClassLoader(cl);
+                    receiver.setPendingResult(this);
                     receiver.onReceive(mContext, intent);
                 } catch (Exception e) {
-                    if (mRegistered && mCurOrdered) {
-                        try {
-                            if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
-                                    "Finishing failed broadcast to " + mReceiver);
-                            mgr.finishReceiver(mIIntentReceiver,
-                                    mCurCode, mCurData, mCurMap, false);
-                        } catch (RemoteException ex) {
-                        }
+                    if (mRegistered && ordered) {
+                        if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+                                "Finishing failed broadcast to " + mReceiver);
+                        sendFinished(mgr);
                     }
                     if (mInstrumentation == null ||
                             !mInstrumentation.onException(mReceiver, e)) {
@@ -724,17 +717,9 @@
                             + " in " + mReceiver, e);
                     }
                 }
-                if (mRegistered && mCurOrdered) {
-                    try {
-                        if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
-                                "Finishing broadcast to " + mReceiver);
-                        mgr.finishReceiver(mIIntentReceiver,
-                                receiver.getResultCode(),
-                                receiver.getResultData(),
-                                receiver.getResultExtras(false),
-                                receiver.getAbortBroadcast());
-                    } catch (RemoteException ex) {
-                    }
+                
+                if (receiver.getPendingResult() != null) {
+                    finish();
                 }
             }
         }
@@ -798,23 +783,13 @@
                 Slog.i(ActivityThread.TAG, "Enqueueing broadcast " + intent.getAction() + " seq=" + seq
                         + " to " + mReceiver);
             }
-            Args args = new Args();
-            args.mCurIntent = intent;
-            args.mCurCode = resultCode;
-            args.mCurData = data;
-            args.mCurMap = extras;
-            args.mCurOrdered = ordered;
-            args.mCurSticky = sticky;
+            Args args = new Args(intent, resultCode, data, extras, ordered, sticky);
             if (!mActivityThread.post(args)) {
                 if (mRegistered && ordered) {
                     IActivityManager mgr = ActivityManagerNative.getDefault();
-                    try {
-                        if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
-                                "Finishing sync broadcast to " + mReceiver);
-                        mgr.finishReceiver(mIIntentReceiver, args.mCurCode,
-                                args.mCurData, args.mCurMap, false);
-                    } catch (RemoteException ex) {
-                    }
+                    if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+                            "Finishing sync broadcast to " + mReceiver);
+                    args.sendFinished(mgr);
                 }
             }
         }
diff --git a/core/java/android/app/QueuedWork.java b/core/java/android/app/QueuedWork.java
index af6bb1b..6ee4780 100644
--- a/core/java/android/app/QueuedWork.java
+++ b/core/java/android/app/QueuedWork.java
@@ -88,4 +88,14 @@
             toFinish.run();
         }
     }
+    
+    /**
+     * Returns true if there is pending work to be done.  Note that the
+     * result is out of data as soon as you receive it, so be careful how you
+     * use it.
+     */
+    public static boolean hasPendingWork() {
+        return !sPendingWorkFinishers.isEmpty();
+    }
+    
 }
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index b63d026..5939643 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -17,11 +17,14 @@
 package android.content;
 
 import android.app.ActivityManagerNative;
+import android.app.ActivityThread;
 import android.app.IActivityManager;
+import android.app.QueuedWork;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.Slog;
 
 /**
  * Base class for code that will receive intents sent by sendBroadcast().
@@ -160,6 +163,226 @@
  * the containing process active for the entire time of your operation.
  */
 public abstract class BroadcastReceiver {
+    private PendingResult mPendingResult;
+    private boolean mDebugUnregister;
+    
+    /**
+     * State for a result that is pending for a broadcast receiver.  Returned
+     * by {@link BroadcastReceiver#goAsync() goAsync()}
+     * while in {@link BroadcastReceiver#onReceive BroadcastReceiver.onReceive()}.
+     */
+    public static class PendingResult {
+        /** @hide */
+        public static final int TYPE_COMPONENT = 0;
+        /** @hide */
+        public static final int TYPE_REGISTERED = 1;
+        /** @hide */
+        public static final int TYPE_UNREGISTERED = 2;
+        
+        final int mType;
+        final boolean mOrderedHint;
+        final boolean mInitialStickyHint;
+        final IBinder mToken;
+        
+        int mResultCode;
+        String mResultData;
+        Bundle mResultExtras;
+        boolean mAbortBroadcast;
+        boolean mFinished;
+        
+        /** @hide */
+        public PendingResult(int resultCode, String resultData, Bundle resultExtras,
+                int type, boolean ordered, boolean sticky, IBinder token) {
+            mResultCode = resultCode;
+            mResultData = resultData;
+            mResultExtras = resultExtras;
+            mType = type;
+            mOrderedHint = ordered;
+            mInitialStickyHint = sticky;
+            mToken = token;
+        }
+        
+        /**
+         * Version of {@link BroadcastReceiver#setResultCode(int)
+         * BroadcastReceiver.setResultCode(int)} for
+         * asynchronous broadcast handling.
+         */
+        public final void setResultCode(int code) {
+            checkSynchronousHint();
+            mResultCode = code;
+        }
+
+        /**
+         * Version of {@link BroadcastReceiver#getResultCode()
+         * BroadcastReceiver.getResultCode()} for
+         * asynchronous broadcast handling.
+         */
+        public final int getResultCode() {
+            return mResultCode;
+        }
+
+        /**
+         * Version of {@link BroadcastReceiver#setResultData(String)
+         * BroadcastReceiver.setResultData(String)} for
+         * asynchronous broadcast handling.
+         */
+        public final void setResultData(String data) {
+            checkSynchronousHint();
+            mResultData = data;
+        }
+
+        /**
+         * Version of {@link BroadcastReceiver#getResultData()
+         * BroadcastReceiver.getResultData()} for
+         * asynchronous broadcast handling.
+         */
+        public final String getResultData() {
+            return mResultData;
+        }
+
+        /**
+         * Version of {@link BroadcastReceiver#setResultExtras(Bundle)
+         * BroadcastReceiver.setResultExtras(Bundle)} for
+         * asynchronous broadcast handling.
+         */
+        public final void setResultExtras(Bundle extras) {
+            checkSynchronousHint();
+            mResultExtras = extras;
+        }
+
+        /**
+         * Version of {@link BroadcastReceiver#getResultExtras(boolean)
+         * BroadcastReceiver.getResultExtras(boolean)} for
+         * asynchronous broadcast handling.
+         */
+        public final Bundle getResultExtras(boolean makeMap) {
+            Bundle e = mResultExtras;
+            if (!makeMap) return e;
+            if (e == null) mResultExtras = e = new Bundle();
+            return e;
+        }
+
+        /**
+         * Version of {@link BroadcastReceiver#setResult(int, String, Bundle)
+         * BroadcastReceiver.setResult(int, String, Bundle)} for
+         * asynchronous broadcast handling.
+         */
+        public final void setResult(int code, String data, Bundle extras) {
+            checkSynchronousHint();
+            mResultCode = code;
+            mResultData = data;
+            mResultExtras = extras;
+        }
+     
+        /**
+         * Version of {@link BroadcastReceiver#getAbortBroadcast()
+         * BroadcastReceiver.getAbortBroadcast()} for
+         * asynchronous broadcast handling.
+         */
+        public final boolean getAbortBroadcast() {
+            return mAbortBroadcast;
+        }
+
+        /**
+         * Version of {@link BroadcastReceiver#abortBroadcast()
+         * BroadcastReceiver.abortBroadcast()} for
+         * asynchronous broadcast handling.
+         */
+        public final void abortBroadcast() {
+            checkSynchronousHint();
+            mAbortBroadcast = true;
+        }
+        
+        /**
+         * Version of {@link BroadcastReceiver#clearAbortBroadcast()
+         * BroadcastReceiver.clearAbortBroadcast()} for
+         * asynchronous broadcast handling.
+         */
+        public final void clearAbortBroadcast() {
+            mAbortBroadcast = false;
+        }
+        
+        /**
+         * Finish the broadcast.  The current result will be sent and the
+         * next broadcast will proceed.
+         */
+        public final void finish() {
+            if (mType == TYPE_COMPONENT) {
+                final IActivityManager mgr = ActivityManagerNative.getDefault();
+                if (QueuedWork.hasPendingWork()) {
+                    // If this is a broadcast component, we need to make sure any
+                    // queued work is complete before telling AM we are done, so
+                    // we don't have our process killed before that.  We now know
+                    // there is pending work; put another piece of work at the end
+                    // of the list to finish the broadcast, so we don't block this
+                    // thread (which may be the main thread) to have it finished.
+                    //
+                    // Note that we don't need to use QueuedWork.add() with the
+                    // runnable, since we know the AM is waiting for us until the
+                    // executor gets to it.
+                    QueuedWork.singleThreadExecutor().execute( new Runnable() {
+                        @Override public void run() {
+                            if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+                                    "Finishing broadcast after work to component " + mToken);
+                            sendFinished(mgr);
+                        }
+                    });
+                } else {
+                    if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+                            "Finishing broadcast to component " + mToken);
+                    sendFinished(mgr);
+                }
+            } else if (mOrderedHint && mType != TYPE_UNREGISTERED) {
+                if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+                        "Finishing broadcast to " + mToken);
+                final IActivityManager mgr = ActivityManagerNative.getDefault();
+                sendFinished(mgr);
+            }
+        }
+        
+        /** @hide */
+        public void setExtrasClassLoader(ClassLoader cl) {
+            if (mResultExtras != null) {
+                mResultExtras.setClassLoader(cl);
+            }
+        }
+        
+        /** @hide */
+        public void sendFinished(IActivityManager am) {
+            synchronized (this) {
+                if (mFinished) {
+                    throw new IllegalStateException("Broadcast already finished");
+                }
+                mFinished = true;
+            
+                try {
+                    if (mOrderedHint) {
+                        am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,
+                                mAbortBroadcast);
+                    } else {
+                        // This broadcast was sent to a component; it is not ordered,
+                        // but we still need to tell the activity manager we are done.
+                        am.finishReceiver(mToken, 0, null, null, false);
+                    }
+                } catch (RemoteException ex) {
+                }
+            }
+        }
+        
+        void checkSynchronousHint() {
+            // Note that we don't assert when receiving the initial sticky value,
+            // since that may have come from an ordered broadcast.  We'll catch
+            // them later when the real broadcast happens again.
+            if (mOrderedHint || mInitialStickyHint) {
+                return;
+            }
+            RuntimeException e = new RuntimeException(
+                    "BroadcastReceiver trying to return result during a non-ordered broadcast");
+            e.fillInStackTrace();
+            Log.e("BroadcastReceiver", e.getMessage(), e);
+        }
+    }
+    
     public BroadcastReceiver() {
     }
 
@@ -197,6 +420,26 @@
     public abstract void onReceive(Context context, Intent intent);
 
     /**
+     * This can be called by an application in {@link #onReceive} to allow
+     * it to keep the broadcast active after returning from that function.
+     * This does <em>not</em> change the expectation of being relatively
+     * responsive to the broadcast (finishing it within 10s), but does allow
+     * the implementation to move work related to it over to another thread
+     * to avoid glitching the main UI thread due to disk IO.
+     * 
+     * @return Returns a {@link PendingResult} representing the result of
+     * the active broadcast.  The BroadcastRecord itself is no longer active;
+     * all data and other interaction must go through {@link PendingResult}
+     * APIs.  The {@link PendingResult#finish PendingResult.finish()} method
+     * must be called once processing of the broadcast is done.
+     */
+    public final PendingResult goAsync() {
+        PendingResult res = mPendingResult;
+        mPendingResult = null;
+        return res;
+    }
+    
+    /**
      * Provide a binder to an already-running service.  This method is synchronous
      * and will not start the target service if it is not present, so it is safe
      * to call from {@link #onReceive}.
@@ -225,9 +468,9 @@
      * {@link android.app.Activity#RESULT_OK} constants, though the
      * actual meaning of this value is ultimately up to the broadcaster.
      * 
-     * <p><strong>This method does not work with non-ordered broadcasts such
+     * <p class="note">This method does not work with non-ordered broadcasts such
      * as those sent with {@link Context#sendBroadcast(Intent)
-     * Context.sendBroadcast}</strong></p>
+     * Context.sendBroadcast}</p>
      * 
      * @param code The new result code.
      * 
@@ -235,7 +478,7 @@
      */
     public final void setResultCode(int code) {
         checkSynchronousHint();
-        mResultCode = code;
+        mPendingResult.mResultCode = code;
     }
 
     /**
@@ -244,7 +487,7 @@
      * @return int The current result code.
      */
     public final int getResultCode() {
-        return mResultCode;
+        return mPendingResult != null ? mPendingResult.mResultCode : 0;
     }
 
     /**
@@ -264,7 +507,7 @@
      */
     public final void setResultData(String data) {
         checkSynchronousHint();
-        mResultData = data;
+        mPendingResult.mResultData = data;
     }
 
     /**
@@ -274,7 +517,7 @@
      * @return String The current result data; may be null.
      */
     public final String getResultData() {
-        return mResultData;
+        return mPendingResult != null ? mPendingResult.mResultData : null;
     }
 
     /**
@@ -296,7 +539,7 @@
      */
     public final void setResultExtras(Bundle extras) {
         checkSynchronousHint();
-        mResultExtras = extras;
+        mPendingResult.mResultExtras = extras;
     }
 
     /**
@@ -311,9 +554,12 @@
      * @return Map The current extras map.
      */
     public final Bundle getResultExtras(boolean makeMap) {
-        Bundle e = mResultExtras;
+        if (mPendingResult == null) {
+            return null;
+        }
+        Bundle e = mPendingResult.mResultExtras;
         if (!makeMap) return e;
-        if (e == null) mResultExtras = e = new Bundle();
+        if (e == null) mPendingResult.mResultExtras = e = new Bundle();
         return e;
     }
 
@@ -341,9 +587,9 @@
      */
     public final void setResult(int code, String data, Bundle extras) {
         checkSynchronousHint();
-        mResultCode = code;
-        mResultData = data;
-        mResultExtras = extras;
+        mPendingResult.mResultCode = code;
+        mPendingResult.mResultData = data;
+        mPendingResult.mResultExtras = extras;
     }
  
     /**
@@ -353,7 +599,7 @@
      * @return True if the broadcast should be aborted.
      */
     public final boolean getAbortBroadcast() {
-        return mAbortBroadcast;
+        return mPendingResult != null ? mPendingResult.mAbortBroadcast : false;
     }
 
     /**
@@ -372,7 +618,7 @@
      */
     public final void abortBroadcast() {
         checkSynchronousHint();
-        mAbortBroadcast = true;
+        mPendingResult.mAbortBroadcast = true;
     }
     
     /**
@@ -380,7 +626,9 @@
      * broadcast.
      */
     public final void clearAbortBroadcast() {
-        mAbortBroadcast = false;
+        if (mPendingResult != null) {
+            mPendingResult.mAbortBroadcast = false;
+        }
     }
     
     /**
@@ -388,7 +636,7 @@
      * broadcast.
      */
     public final boolean isOrderedBroadcast() {
-        return mOrderedHint;
+        return mPendingResult != null ? mPendingResult.mOrderedHint : false;
     }
     
     /**
@@ -398,7 +646,7 @@
      * not directly the result of a broadcast right now.
      */
     public final boolean isInitialStickyBroadcast() {
-        return mInitialStickyHint;
+        return mPendingResult != null ? mPendingResult.mInitialStickyHint : false;
     }
     
     /**
@@ -406,15 +654,21 @@
      * running in ordered mode.
      */
     public final void setOrderedHint(boolean isOrdered) {
-        mOrderedHint = isOrdered;
+        // Accidentally left in the SDK.
     }
     
     /**
-     * For internal use, sets the hint about whether this BroadcastReceiver is
-     * receiving the initial sticky broadcast value. @hide
+     * For internal use to set the result data that is active. @hide
      */
-    public final void setInitialStickyHint(boolean isInitialSticky) {
-        mInitialStickyHint = isInitialSticky;
+    public final void setPendingResult(PendingResult result) {
+        mPendingResult = result;
+    }
+    
+    /**
+     * For internal use to set the result data that is active. @hide
+     */
+    public final PendingResult getPendingResult() {
+        return mPendingResult;
     }
     
     /**
@@ -440,10 +694,14 @@
     }
     
     void checkSynchronousHint() {
+        if (mPendingResult == null) {
+            throw new IllegalStateException("Call while result is not pending");
+        }
+        
         // Note that we don't assert when receiving the initial sticky value,
         // since that may have come from an ordered broadcast.  We'll catch
         // them later when the real broadcast happens again.
-        if (mOrderedHint || mInitialStickyHint) {
+        if (mPendingResult.mOrderedHint || mPendingResult.mInitialStickyHint) {
             return;
         }
         RuntimeException e = new RuntimeException(
@@ -451,13 +709,5 @@
         e.fillInStackTrace();
         Log.e("BroadcastReceiver", e.getMessage(), e);
     }
-    
-    private int mResultCode;
-    private String mResultData;
-    private Bundle mResultExtras;
-    private boolean mAbortBroadcast;
-    private boolean mDebugUnregister;
-    private boolean mOrderedHint;
-    private boolean mInitialStickyHint;
 }