From e0e413e2b17a0164e15c77f4ab51b3166f9111d2 Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Wed, 9 Dec 2015 17:22:26 -0800 Subject: Add new target SDK filtering feature to BroadcastOptions. You can now control the range of target SDKs that receivers will be need to have in order to receive your broadcast. Use this for CONNECTIVITY_ACTION to not allow N+ applications to receive these broadcasts through their manifest. Also tweak the broadcast debug output code to now include the disposition of each receiver in the list. This is becoming important as skipping receivers is becoming a more common thing to have happen. Change-Id: I251daf68575c07cbb447536286ab4e68b7015148 --- core/java/android/app/BroadcastOptions.java | 65 ++++++++++- core/java/android/app/ContextImpl.java | 18 +++- core/java/android/content/Context.java | 8 ++ core/java/android/content/ContextWrapper.java | 7 ++ .../com/android/server/ConnectivityService.java | 9 +- .../java/com/android/server/am/BroadcastQueue.java | 120 ++++++++++++--------- .../com/android/server/am/BroadcastRecord.java | 31 ++++-- .../server/BroadcastInterceptingContext.java | 6 ++ test-runner/src/android/test/mock/MockContext.java | 6 ++ .../layoutlib/bridge/android/BridgeContext.java | 5 + 10 files changed, 212 insertions(+), 63 deletions(-) diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index 1f378da4afd3..175b9799c5db 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -17,6 +17,7 @@ package android.app; import android.annotation.SystemApi; +import android.os.Build; import android.os.Bundle; /** @@ -28,15 +29,28 @@ import android.os.Bundle; @SystemApi public class BroadcastOptions { private long mTemporaryAppWhitelistDuration; + private int mMinManifestReceiverApiLevel = 0; + private int mMaxManifestReceiverApiLevel = Build.VERSION_CODES.CUR_DEVELOPMENT; /** * How long to temporarily put an app on the power whitelist when executing this broadcast * to it. - * @hide */ - public static final String KEY_TEMPORARY_APP_WHITELIST_DURATION + static final String KEY_TEMPORARY_APP_WHITELIST_DURATION = "android:broadcast.temporaryAppWhitelistDuration"; + /** + * Corresponds to {@link #setMinManifestReceiverApiLevel}. + */ + static final String KEY_MIN_MANIFEST_RECEIVER_API_LEVEL + = "android:broadcast.minManifestReceiverApiLevel"; + + /** + * Corresponds to {@link #setMaxManifestReceiverApiLevel}. + */ + static final String KEY_MAX_MANIFEST_RECEIVER_API_LEVEL + = "android:broadcast.maxManifestReceiverApiLevel"; + public static BroadcastOptions makeBasic() { BroadcastOptions opts = new BroadcastOptions(); return opts; @@ -48,6 +62,9 @@ public class BroadcastOptions { /** @hide */ public BroadcastOptions(Bundle opts) { mTemporaryAppWhitelistDuration = opts.getLong(KEY_TEMPORARY_APP_WHITELIST_DURATION); + mMinManifestReceiverApiLevel = opts.getInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, 0); + mMaxManifestReceiverApiLevel = opts.getInt(KEY_MAX_MANIFEST_RECEIVER_API_LEVEL, + Build.VERSION_CODES.CUR_DEVELOPMENT); } /** @@ -67,11 +84,47 @@ public class BroadcastOptions { return mTemporaryAppWhitelistDuration; } + /** + * Set the minimum target API level of receivers of the broadcast. If an application + * is targeting an API level less than this, the broadcast will not be delivered to + * them. This only applies to receivers declared in the app's AndroidManifest.xml. + * @hide + */ + public void setMinManifestReceiverApiLevel(int apiLevel) { + mMinManifestReceiverApiLevel = apiLevel; + } + + /** + * Return {@link #setMinManifestReceiverApiLevel}. + * @hide + */ + public int getMinManifestReceiverApiLevel() { + return mMinManifestReceiverApiLevel; + } + + /** + * Set the maximum target API level of receivers of the broadcast. If an application + * is targeting an API level greater than this, the broadcast will not be delivered to + * them. This only applies to receivers declared in the app's AndroidManifest.xml. + * @hide + */ + public void setMaxManifestReceiverApiLevel(int apiLevel) { + mMaxManifestReceiverApiLevel = apiLevel; + } + + /** + * Return {@link #setMaxManifestReceiverApiLevel}. + * @hide + */ + public int getMaxManifestReceiverApiLevel() { + return mMaxManifestReceiverApiLevel; + } + /** * Returns the created options as a Bundle, which can be passed to * {@link android.content.Context#sendBroadcast(android.content.Intent) * Context.sendBroadcast(Intent)} and related methods. - * Note that the returned Bundle is still owned by the ActivityOptions + * Note that the returned Bundle is still owned by the BroadcastOptions * object; you must not modify it, but can supply it to the sendBroadcast * methods that take an options Bundle. */ @@ -80,6 +133,12 @@ public class BroadcastOptions { if (mTemporaryAppWhitelistDuration > 0) { b.putLong(KEY_TEMPORARY_APP_WHITELIST_DURATION, mTemporaryAppWhitelistDuration); } + if (mMinManifestReceiverApiLevel != 0) { + b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel); + } + if (mMaxManifestReceiverApiLevel != Build.VERSION_CODES.CUR_DEVELOPMENT) { + b.putInt(KEY_MAX_MANIFEST_RECEIVER_API_LEVEL, mMaxManifestReceiverApiLevel); + } return b.isEmpty() ? null : b; } } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 36e98f9f0567..611a9ffe7425 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1092,7 +1092,23 @@ class ContextImpl extends Context { intent.prepareToLeaveProcess(); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, - Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, true, user.getIdentifier()); + Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, true, + user.getIdentifier()); + } catch (RemoteException e) { + throw new RuntimeException("Failure from system", e); + } + } + + @Override + @Deprecated + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) { + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + try { + intent.prepareToLeaveProcess(); + ActivityManagerNative.getDefault().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, + Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, options, false, true, + user.getIdentifier()); } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 1f7fd9dee9ba..eca9c6377b73 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2115,6 +2115,14 @@ public abstract class Context { public abstract void sendStickyBroadcastAsUser(@RequiresPermission Intent intent, UserHandle user); + /** + * @hide + * This is just here for sending CONNECTIVITY_ACTION. + */ + @Deprecated + public abstract void sendStickyBroadcastAsUser(@RequiresPermission Intent intent, + UserHandle user, Bundle options); + /** *

Version of * {@link #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle)} diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 73d0ddc46fac..8ad01dad7234 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -536,6 +536,13 @@ public class ContextWrapper extends Context { mBase.sendStickyBroadcastAsUser(intent, user); } + /** @hide */ + @Override + @Deprecated + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) { + mBase.sendStickyBroadcastAsUser(intent, user, options); + } + @Override @Deprecated public void sendStickyOrderedBroadcastAsUser(Intent intent, diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index c712a568c480..ed64c2b37a4a 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -34,6 +34,7 @@ import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import android.annotation.Nullable; import android.app.AlarmManager; +import android.app.BroadcastOptions; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -72,6 +73,7 @@ import android.net.RouteInfo; import android.net.UidRange; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.FileUtils; import android.os.Handler; @@ -1529,6 +1531,7 @@ public class ConnectivityService extends IConnectivityManager.Stub log("sendStickyBroadcast: action=" + intent.getAction()); } + Bundle options = null; final long ident = Binder.clearCallingIdentity(); if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { final NetworkInfo ni = intent.getParcelableExtra( @@ -1536,6 +1539,10 @@ public class ConnectivityService extends IConnectivityManager.Stub if (ni.getType() == ConnectivityManager.TYPE_MOBILE_SUPL) { intent.setAction(ConnectivityManager.CONNECTIVITY_ACTION_SUPL); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + } else { + BroadcastOptions opts = BroadcastOptions.makeBasic(); + opts.setMaxManifestReceiverApiLevel(Build.VERSION_CODES.M); + options = opts.toBundle(); } final IBatteryStats bs = BatteryStatsService.getService(); try { @@ -1546,7 +1553,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } } try { - mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL, options); } finally { Binder.restoreCallingIdentity(ident); } diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 39e25ee482f9..3be92c88180c 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -468,7 +468,7 @@ public final class BroadcastQueue { } private void deliverToRegisteredReceiverLocked(BroadcastRecord r, - BroadcastFilter filter, boolean ordered) { + BroadcastFilter filter, boolean ordered, int index) { boolean skip = false; if (filter.requiredPermission != null) { int perm = mService.checkComponentPermission(filter.requiredPermission, @@ -576,64 +576,70 @@ public final class BroadcastQueue { if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid, r.callingPid, r.resolvedType, filter.receiverList.uid)) { - return; + skip = true; } - if (filter.receiverList.app == null || filter.receiverList.app.crashing) { + if (!skip && (filter.receiverList.app == null || filter.receiverList.app.crashing)) { Slog.w(TAG, "Skipping deliver [" + mQueueName + "] " + r + " to " + filter.receiverList + ": process crashing"); skip = true; } - if (!skip) { - // If permissions need a review before any of the app components can run, we drop - // the broadcast and if the calling app is in the foreground and the broadcast is - // explicit we launch the review UI passing it a pending intent to send the skipped - // broadcast. - if (Build.PERMISSIONS_REVIEW_REQUIRED) { - if (!requestStartTargetPermissionsReviewIfNeededLocked(r, filter.packageName, - filter.owningUserId)) { - return; - } + if (skip) { + r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED; + return; + } + + // If permissions need a review before any of the app components can run, we drop + // the broadcast and if the calling app is in the foreground and the broadcast is + // explicit we launch the review UI passing it a pending intent to send the skipped + // broadcast. + if (Build.PERMISSIONS_REVIEW_REQUIRED) { + if (!requestStartTargetPermissionsReviewIfNeededLocked(r, filter.packageName, + filter.owningUserId)) { + r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED; + return; } + } - // If this is not being sent as an ordered broadcast, then we - // don't want to touch the fields that keep track of the current - // state of ordered broadcasts. + r.delivery[index] = BroadcastRecord.DELIVERY_DELIVERED; + + // If this is not being sent as an ordered broadcast, then we + // don't want to touch the fields that keep track of the current + // state of ordered broadcasts. + if (ordered) { + r.receiver = filter.receiverList.receiver.asBinder(); + r.curFilter = filter; + filter.receiverList.curBroadcast = r; + r.state = BroadcastRecord.CALL_IN_RECEIVE; + if (filter.receiverList.app != null) { + // Bump hosting application to no longer be in background + // scheduling class. Note that we can't do that if there + // isn't an app... but we can only be in that case for + // things that directly call the IActivityManager API, which + // are already core system stuff so don't matter for this. + r.curApp = filter.receiverList.app; + filter.receiverList.app.curReceiver = r; + mService.updateOomAdjLocked(r.curApp); + } + } + try { + if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST, + "Delivering to " + filter + " : " + r); + performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver, + new Intent(r.intent), r.resultCode, r.resultData, + r.resultExtras, r.ordered, r.initialSticky, r.userId); if (ordered) { - r.receiver = filter.receiverList.receiver.asBinder(); - r.curFilter = filter; - filter.receiverList.curBroadcast = r; - r.state = BroadcastRecord.CALL_IN_RECEIVE; - if (filter.receiverList.app != null) { - // Bump hosting application to no longer be in background - // scheduling class. Note that we can't do that if there - // isn't an app... but we can only be in that case for - // things that directly call the IActivityManager API, which - // are already core system stuff so don't matter for this. - r.curApp = filter.receiverList.app; - filter.receiverList.app.curReceiver = r; - mService.updateOomAdjLocked(r.curApp); - } + r.state = BroadcastRecord.CALL_DONE_RECEIVE; } - try { - if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST, - "Delivering to " + filter + " : " + r); - performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver, - new Intent(r.intent), r.resultCode, r.resultData, - r.resultExtras, r.ordered, r.initialSticky, r.userId); - if (ordered) { - r.state = BroadcastRecord.CALL_DONE_RECEIVE; - } - } catch (RemoteException e) { - Slog.w(TAG, "Failure sending broadcast " + r.intent, e); - if (ordered) { - r.receiver = null; - r.curFilter = null; - filter.receiverList.curBroadcast = null; - if (filter.receiverList.app != null) { - filter.receiverList.app.curReceiver = null; - } + } catch (RemoteException e) { + Slog.w(TAG, "Failure sending broadcast " + r.intent, e); + if (ordered) { + r.receiver = null; + r.curFilter = null; + filter.receiverList.curBroadcast = null; + if (filter.receiverList.app != null) { + filter.receiverList.app.curReceiver = null; } } } @@ -740,7 +746,7 @@ public final class BroadcastQueue { if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Delivering non-ordered on [" + mQueueName + "] to registered " + target + ": " + r); - deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false); + deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false, i); } addBroadcastToHistoryLocked(r); if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Done with parallel broadcast [" @@ -897,7 +903,7 @@ public final class BroadcastQueue { "Delivering ordered [" + mQueueName + "] to registered " + filter + ": " + r); - deliverToRegisteredReceiverLocked(r, filter, r.ordered); + deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx); if (r.receiver == null || !r.ordered) { // The receiver has already finished, so schedule to // process the next one. @@ -925,10 +931,17 @@ public final class BroadcastQueue { info.activityInfo.name); boolean skip = false; + if (brOptions != null && + (info.activityInfo.applicationInfo.targetSdkVersion + < brOptions.getMinManifestReceiverApiLevel() || + info.activityInfo.applicationInfo.targetSdkVersion + > brOptions.getMaxManifestReceiverApiLevel())) { + skip = true; + } int perm = mService.checkComponentPermission(info.activityInfo.permission, r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid, info.activityInfo.exported); - if (perm != PackageManager.PERMISSION_GRANTED) { + if (!skip && perm != PackageManager.PERMISSION_GRANTED) { if (!info.activityInfo.exported) { Slog.w(TAG, "Permission Denial: broadcasting " + r.intent.toString() @@ -945,7 +958,7 @@ public final class BroadcastQueue { + " due to receiver " + component.flattenToShortString()); } skip = true; - } else if (info.activityInfo.permission != null) { + } else if (!skip && info.activityInfo.permission != null) { final int opCode = AppOpsManager.permissionToOpCode(info.activityInfo.permission); if (opCode != AppOpsManager.OP_NONE && mService.mAppOpsService.noteOperation(opCode, r.callingUid, @@ -1109,6 +1122,7 @@ public final class BroadcastQueue { if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Skipping delivery of ordered [" + mQueueName + "] " + r + " for whatever reason"); + r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED; r.receiver = null; r.curFilter = null; r.state = BroadcastRecord.IDLE; @@ -1116,6 +1130,7 @@ public final class BroadcastQueue { return; } + r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED; r.state = BroadcastRecord.APP_RECEIVE; r.curComponent = component; final int receiverUid = info.activityInfo.applicationInfo.uid; @@ -1291,6 +1306,7 @@ public final class BroadcastQueue { String anrMessage = null; Object curReceiver = r.receivers.get(r.nextReceiver-1); + r.delivery[r.nextReceiver-1] = BroadcastRecord.DELIVERY_TIMEOUT; Slog.w(TAG, "Receiver during timeout: " + curReceiver); logBroadcastReceiverDiscardLocked(r); if (curReceiver instanceof BroadcastFilter) { diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 1a269cf840f7..e99cbf9e563f 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -57,6 +57,7 @@ final class BroadcastRecord extends Binder { final int appOp; // an app op that is associated with this broadcast final BroadcastOptions options; // BroadcastOptions supplied by caller final List receivers; // contains BroadcastFilter and ResolveInfo + final int[] delivery; // delivery state of each receiver IIntentReceiver resultTo; // who receives final result if non-null long enqueueClockTime; // the clock time the broadcast was enqueued long dispatchTime; // when dispatch started on this set of receivers @@ -79,6 +80,11 @@ final class BroadcastRecord extends Binder { static final int CALL_DONE_RECEIVE = 3; static final int WAITING_SERVICES = 4; + static final int DELIVERY_PENDING = 0; + static final int DELIVERY_DELIVERED = 1; + static final int DELIVERY_SKIPPED = 2; + static final int DELIVERY_TIMEOUT = 3; + // The following are set when we are calling a receiver (one that // was found in our list of registered receivers). BroadcastFilter curFilter; @@ -183,12 +189,24 @@ final class BroadcastRecord extends Binder { PrintWriterPrinter printer = new PrintWriterPrinter(pw); for (int i = 0; i < N; i++) { Object o = receivers.get(i); - pw.print(prefix); pw.print("Receiver #"); pw.print(i); - pw.print(": "); pw.println(o); - if (o instanceof BroadcastFilter) - ((BroadcastFilter)o).dumpBrief(pw, p2); - else if (o instanceof ResolveInfo) - ((ResolveInfo)o).dump(printer, p2, 0); + pw.print(prefix); + switch (delivery[i]) { + case DELIVERY_PENDING: pw.print("Pending"); break; + case DELIVERY_DELIVERED: pw.print("Deliver"); break; + case DELIVERY_SKIPPED: pw.print("Skipped"); break; + case DELIVERY_TIMEOUT: pw.print("Timeout"); break; + default: pw.print("???????"); break; + } + pw.print(" #"); pw.print(i); pw.print(": "); + if (o instanceof BroadcastFilter) { + pw.println(o); + ((BroadcastFilter) o).dumpBrief(pw, p2); + } else if (o instanceof ResolveInfo) { + pw.println("(manifest)"); + ((ResolveInfo) o).dump(printer, p2, 0); + } else { + pw.println(o); + } } } @@ -211,6 +229,7 @@ final class BroadcastRecord extends Binder { appOp = _appOp; options = _options; receivers = _receivers; + delivery = new int[_receivers != null ? _receivers.size() : 0]; resultTo = _resultTo; resultCode = _resultCode; resultData = _resultData; diff --git a/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java b/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java index 757f1c6ea39d..13657ab7f02d 100644 --- a/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java +++ b/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentFilter; +import android.os.Bundle; import android.os.Handler; import android.os.UserHandle; @@ -164,6 +165,11 @@ public class BroadcastInterceptingContext extends ContextWrapper { sendBroadcast(intent); } + @Override + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) { + sendBroadcast(intent); + } + @Override public void removeStickyBroadcast(Intent intent) { // ignored diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java index 4c3b5987f05d..eee132bfe490 100644 --- a/test-runner/src/android/test/mock/MockContext.java +++ b/test-runner/src/android/test/mock/MockContext.java @@ -433,6 +433,12 @@ public class MockContext extends Context { throw new UnsupportedOperationException(); } + /** @hide */ + @Override + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) { + throw new UnsupportedOperationException(); + } + @Override public void sendStickyOrderedBroadcastAsUser(Intent intent, UserHandle user, BroadcastReceiver resultReceiver, diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index bd5335ebb38d..45bcf1d033a2 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -1633,6 +1633,11 @@ public final class BridgeContext extends Context { // pass } + @Override + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) { + // pass + } + @Override public void sendStickyOrderedBroadcastAsUser(Intent intent, UserHandle user, BroadcastReceiver resultReceiver, -- cgit v1.2.3-59-g8ed1b