From 32ee8eefd753398186c6967febc6d4d001f9c1cb Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Wed, 8 Mar 2017 20:17:51 -0700 Subject: [PATCH] Create new BroadcastBehavior annotation. This will be used to help document the expected behavior of various broadcast actions defined by the OS. Also add logic to PackageParser that will then yell at developers whose manifests are making bad assumptions about which broadcasts they'll receive. Test: builds, boots Bug: 35925551 Change-Id: I059c2bf8aa3ce53d9ff18dcc263db7620cd14bd6 --- .../java/android/accounts/AccountManager.java | 5 ++ .../android/annotation/BroadcastBehavior.java | 56 +++++++++++++++++++ .../app/admin/DeviceAdminReceiver.java | 21 ++++++- .../android/appwidget/AppWidgetManager.java | 21 +++++++ core/java/android/content/Intent.java | 2 + .../android/content/pm/PackageParser.java | 20 +++++++ 6 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 core/java/android/annotation/BroadcastBehavior.java diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index e5df278f2cc1d..75d4f32055e27 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -21,8 +21,11 @@ import static android.Manifest.permission.GET_ACCOUNTS; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; import android.annotation.Size; import android.annotation.SystemApi; +import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.BroadcastBehavior; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -335,6 +338,8 @@ public class AccountManager { * * @deprecated use #addOnAccountsUpdatedListener to get account updates in runtime. */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(includeBackground = true) public static final String LOGIN_ACCOUNTS_CHANGED_ACTION = "android.accounts.LOGIN_ACCOUNTS_CHANGED"; diff --git a/core/java/android/annotation/BroadcastBehavior.java b/core/java/android/annotation/BroadcastBehavior.java new file mode 100644 index 0000000000000..9b2ca3120290f --- /dev/null +++ b/core/java/android/annotation/BroadcastBehavior.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.annotation; + +import android.content.Intent; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Description of how the annotated broadcast action behaves. + * + * @hide + */ +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.SOURCE) +public @interface BroadcastBehavior { + /** + * This broadcast will only be delivered to an explicit target. + * + * @see Intent#setPackage(String) + * @see Intent#setComponent(android.content.ComponentName) + */ + boolean explicitOnly() default false; + + /** + * This broadcast will only be delivered to registered receivers. + * + * @see Intent#FLAG_RECEIVER_REGISTERED_ONLY + */ + boolean registeredOnly() default false; + + /** + * This broadcast will include all {@code AndroidManifest.xml} receivers + * regardless of process state. + * + * @see Intent#FLAG_RECEIVER_INCLUDE_BACKGROUND + */ + boolean includeBackground() default false; +} diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index 0fb59668a201f..d9b6eed4fc4d7 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -17,6 +17,7 @@ package android.app.admin; import android.accounts.AccountManager; +import android.annotation.BroadcastBehavior; import android.annotation.IntDef; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; @@ -81,6 +82,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * that other applications can not abuse it. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_DEVICE_ADMIN_ENABLED = "android.app.action.DEVICE_ADMIN_ENABLED"; @@ -94,6 +96,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * to the user before they disable your admin. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED"; @@ -115,6 +118,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * its intent filter. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED"; @@ -131,6 +135,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * this broadcast. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_PASSWORD_CHANGED = "android.app.action.ACTION_PASSWORD_CHANGED"; @@ -147,6 +152,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * this broadcast. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_PASSWORD_FAILED = "android.app.action.ACTION_PASSWORD_FAILED"; @@ -160,6 +166,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * this broadcast. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_PASSWORD_SUCCEEDED = "android.app.action.ACTION_PASSWORD_SUCCEEDED"; @@ -173,6 +180,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * this broadcast. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_PASSWORD_EXPIRING = "android.app.action.ACTION_PASSWORD_EXPIRING"; @@ -187,6 +195,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @see DevicePolicyManager#isLockTaskPermitted(String) */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_LOCK_TASK_ENTERING = "android.app.action.LOCK_TASK_ENTERING"; @@ -200,6 +209,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @see DevicePolicyManager#isLockTaskPermitted(String) */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_LOCK_TASK_EXITING = "android.app.action.LOCK_TASK_EXITING"; @@ -232,6 +242,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { *

Output: Nothing

*/ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_PROFILE_PROVISIONING_COMPLETE = "android.app.action.PROFILE_PROVISIONING_COMPLETE"; @@ -244,6 +255,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_BUGREPORT_SHARING_DECLINED = "android.app.action.BUGREPORT_SHARING_DECLINED"; @@ -256,6 +268,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_BUGREPORT_FAILED = "android.app.action.BUGREPORT_FAILED"; /** @@ -266,6 +279,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_BUGREPORT_SHARE = "android.app.action.BUGREPORT_SHARE"; @@ -274,6 +288,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_SECURITY_LOGS_AVAILABLE = "android.app.action.SECURITY_LOGS_AVAILABLE"; @@ -283,6 +298,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_NETWORK_LOGS_AVAILABLE = "android.app.action.NETWORK_LOGS_AVAILABLE"; @@ -314,7 +330,8 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_USER_ADDED = "android.app.action.USER_ADDED"; + @BroadcastBehavior(explicitOnly = true) + public static final String ACTION_USER_ADDED = "android.app.action.USER_ADDED"; /** * Broadcast action: notify the device owner that a user or profile has been removed. @@ -323,6 +340,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_USER_REMOVED = "android.app.action.USER_REMOVED"; /** @@ -401,6 +419,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_NOTIFY_PENDING_SYSTEM_UPDATE = "android.app.action.NOTIFY_PENDING_SYSTEM_UPDATE"; diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 67c791df3c9a5..74a39e85e029a 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -16,8 +16,11 @@ package android.appwidget; +import android.annotation.BroadcastBehavior; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -81,12 +84,14 @@ public class AppWidgetManager { * * @see #ACTION_APPWIDGET_CONFIGURE */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_APPWIDGET_PICK = "android.appwidget.action.APPWIDGET_PICK"; /** * Similar to ACTION_APPWIDGET_PICK, but used from keyguard * @hide */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_KEYGUARD_APPWIDGET_PICK = "android.appwidget.action.KEYGUARD_APPWIDGET_PICK"; @@ -133,6 +138,7 @@ public class AppWidgetManager { * @see #ACTION_APPWIDGET_CONFIGURE * */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_APPWIDGET_BIND = "android.appwidget.action.APPWIDGET_BIND"; /** @@ -157,6 +163,7 @@ public class AppWidgetManager { * and not display this AppWidget, and you will receive a {@link #ACTION_APPWIDGET_DELETED} * broadcast. */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_APPWIDGET_CONFIGURE = "android.appwidget.action.APPWIDGET_CONFIGURE"; /** @@ -290,6 +297,8 @@ public class AppWidgetManager { * * @see AppWidgetProvider#onUpdate AppWidgetProvider.onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE"; /** @@ -302,6 +311,8 @@ public class AppWidgetManager { * AppWidgetProvider.onAppWidgetOptionsChanged(Context context, * AppWidgetManager appWidgetManager, int appWidgetId, Bundle newExtras) */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_APPWIDGET_OPTIONS_CHANGED = "android.appwidget.action.APPWIDGET_UPDATE_OPTIONS"; /** @@ -312,6 +323,8 @@ public class AppWidgetManager { * * @see AppWidgetProvider#onDeleted AppWidgetProvider.onDeleted(Context context, int[] appWidgetIds) */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_APPWIDGET_DELETED = "android.appwidget.action.APPWIDGET_DELETED"; /** @@ -322,6 +335,8 @@ public class AppWidgetManager { * * @see AppWidgetProvider#onEnabled AppWidgetProvider.onDisabled(Context context) */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_APPWIDGET_DISABLED = "android.appwidget.action.APPWIDGET_DISABLED"; /** @@ -334,6 +349,8 @@ public class AppWidgetManager { * * @see AppWidgetProvider#onEnabled AppWidgetProvider.onEnabled(Context context) */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_APPWIDGET_ENABLED = "android.appwidget.action.APPWIDGET_ENABLED"; /** @@ -365,6 +382,8 @@ public class AppWidgetManager { * * @see #ACTION_APPWIDGET_HOST_RESTORED */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_APPWIDGET_RESTORED = "android.appwidget.action.APPWIDGET_RESTORED"; @@ -402,6 +421,8 @@ public class AppWidgetManager { * * @see #ACTION_APPWIDGET_RESTORED */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_APPWIDGET_HOST_RESTORED = "android.appwidget.action.APPWIDGET_HOST_RESTORED"; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index d6306e0015162..b559ebfcdd759 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -17,6 +17,7 @@ package android.content; import android.annotation.AnyRes; +import android.annotation.BroadcastBehavior; import android.annotation.IntDef; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; @@ -1991,6 +1992,7 @@ public class Intent implements Parcelable, Cloneable { * This is a protected intent that can only be sent by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(includeBackground = true) public static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED"; /** diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index a1c325ac26eaf..5ae9bb4db6eb9 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -211,6 +211,14 @@ public class PackageParser { CHILD_PACKAGE_TAGS.add(TAG_EAT_COMMENT); } + private static final boolean LOG_UNSAFE_BROADCASTS = false; + + // Set of broadcast actions that are safe for manifest receivers + private static final Set SAFE_BROADCASTS = new ArraySet<>(); + static { + SAFE_BROADCASTS.add(Intent.ACTION_BOOT_COMPLETED); + } + /** @hide */ public static class NewPermissionInfo { public final String name; @@ -4240,6 +4248,18 @@ public class PackageParser { if (intent.isVisibleToInstantApp()) { a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL; } + if (LOG_UNSAFE_BROADCASTS && receiver + && (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O)) { + for (int i = 0; i < intent.countActions(); i++) { + final String action = intent.getAction(i); + if (action == null || !action.startsWith("android.")) continue; + if (!SAFE_BROADCASTS.contains(action)) { + Slog.w(TAG, "Broadcast " + action + " may never be delivered to " + + owner.packageName + " as requested at: " + + parser.getPositionDescription()); + } + } + } } else if (!receiver && parser.getName().equals("preferred")) { ActivityIntentInfo intent = new ActivityIntentInfo(a); if (!parseIntent(res, parser, false /*allowGlobs*/, false /*allowAutoVerify*/,