summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDianne Hackborn <hackbod@google.com>2010-10-26 17:44:01 -0700
committerDianne Hackborn <hackbod@google.com>2010-10-27 17:13:29 -0700
commite829fef63957a23b61cdb01bb692a17a041ff2dc (patch)
tree8e201bd9ef8648eb2d70f700205925f7b000d24f
parent162b689c5257d7e6a937cf734c0edde470c77969 (diff)
downloadframeworks_base-e829fef63957a23b61cdb01bb692a17a041ff2dc.zip
frameworks_base-e829fef63957a23b61cdb01bb692a17a041ff2dc.tar.gz
frameworks_base-e829fef63957a23b61cdb01bb692a17a041ff2dc.tar.bz2
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
-rw-r--r--api/current.xml155
-rw-r--r--core/java/android/app/ActivityThread.java81
-rw-r--r--core/java/android/app/LoadedApk.java95
-rw-r--r--core/java/android/app/QueuedWork.java10
-rw-r--r--core/java/android/content/BroadcastReceiver.java312
5 files changed, 507 insertions, 146 deletions
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 @@ final class RemoteServiceException extends AndroidRuntimeException {
* {@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 @@ public final class ActivityThread {
}
}
- 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 class ActivityThread {
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 @@ public final class ActivityThread {
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 @@ public final class ActivityThread {
+ ", 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 @@ public final class ActivityThread {
}
}
- 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 class LoadedApk {
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 @@ final class LoadedApk {
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 @@ final class LoadedApk {
+ " 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 @@ final class LoadedApk {
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 @@ public class QueuedWork {
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 @@ import android.util.Log;
* 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 class BroadcastReceiver {
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 @@ public abstract class BroadcastReceiver {
* {@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 abstract class BroadcastReceiver {
*/
public final void setResultCode(int code) {
checkSynchronousHint();
- mResultCode = code;
+ mPendingResult.mResultCode = code;
}
/**
@@ -244,7 +487,7 @@ public abstract class BroadcastReceiver {
* @return int The current result code.
*/
public final int getResultCode() {
- return mResultCode;
+ return mPendingResult != null ? mPendingResult.mResultCode : 0;
}
/**
@@ -264,7 +507,7 @@ public abstract class BroadcastReceiver {
*/
public final void setResultData(String data) {
checkSynchronousHint();
- mResultData = data;
+ mPendingResult.mResultData = data;
}
/**
@@ -274,7 +517,7 @@ public abstract class BroadcastReceiver {
* @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 abstract class BroadcastReceiver {
*/
public final void setResultExtras(Bundle extras) {
checkSynchronousHint();
- mResultExtras = extras;
+ mPendingResult.mResultExtras = extras;
}
/**
@@ -311,9 +554,12 @@ public abstract class BroadcastReceiver {
* @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 abstract class BroadcastReceiver {
*/
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 @@ public abstract class BroadcastReceiver {
* @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 abstract class BroadcastReceiver {
*/
public final void abortBroadcast() {
checkSynchronousHint();
- mAbortBroadcast = true;
+ mPendingResult.mAbortBroadcast = true;
}
/**
@@ -380,7 +626,9 @@ public abstract class BroadcastReceiver {
* broadcast.
*/
public final void clearAbortBroadcast() {
- mAbortBroadcast = false;
+ if (mPendingResult != null) {
+ mPendingResult.mAbortBroadcast = false;
+ }
}
/**
@@ -388,7 +636,7 @@ public abstract class BroadcastReceiver {
* broadcast.
*/
public final boolean isOrderedBroadcast() {
- return mOrderedHint;
+ return mPendingResult != null ? mPendingResult.mOrderedHint : false;
}
/**
@@ -398,7 +646,7 @@ public abstract class BroadcastReceiver {
* 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 @@ public abstract class BroadcastReceiver {
* 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 @@ public abstract class BroadcastReceiver {
}
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 @@ public abstract class BroadcastReceiver {
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;
}