diff options
8 files changed, 676 insertions, 86 deletions
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 3bd6451b4c95..248f191cb8b8 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -3437,6 +3437,15 @@ public class AppOpsManager { } /** + * Returns whether the provided {@code op} is a valid op code or not. + * + * @hide + */ + public static boolean isValidOp(int op) { + return op >= 0 && op < sAppOpInfos.length; + } + + /** * @hide */ public static int strDebugOpToOp(String op) { diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index 36035bdcddbc..78beb18263a7 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -832,7 +832,9 @@ class BroadcastQueueImpl extends BroadcastQueue { // If this receiver is going to be skipped, skip it now itself and don't even enqueue // it. - final String skipReason = mSkipPolicy.shouldSkipMessage(r, receiver); + final String skipReason = Flags.avoidNoteOpAtEnqueue() + ? mSkipPolicy.shouldSkipAtEnqueueMessage(r, receiver) + : mSkipPolicy.shouldSkipMessage(r, receiver); if (skipReason != null) { setDeliveryState(null, null, r, i, receiver, BroadcastRecord.DELIVERY_SKIPPED, "skipped by policy at enqueue: " + skipReason); diff --git a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java index d2af84cf3d30..b0d5994cc60b 100644 --- a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java +++ b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java @@ -71,10 +71,20 @@ public class BroadcastSkipPolicy { * {@code null} if it can proceed. */ public @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r, @NonNull Object target) { + return shouldSkipMessage(r, target, false /* preflight */); + } + + public @Nullable String shouldSkipAtEnqueueMessage(@NonNull BroadcastRecord r, + @NonNull Object target) { + return shouldSkipMessage(r, target, true /* preflight */); + } + + private @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r, @NonNull Object target, + boolean preflight) { if (target instanceof BroadcastFilter) { - return shouldSkipMessage(r, (BroadcastFilter) target); + return shouldSkipMessage(r, (BroadcastFilter) target, preflight); } else { - return shouldSkipMessage(r, (ResolveInfo) target); + return shouldSkipMessage(r, (ResolveInfo) target, preflight); } } @@ -86,7 +96,7 @@ public class BroadcastSkipPolicy { * {@code null} if it can proceed. */ private @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r, - @NonNull ResolveInfo info) { + @NonNull ResolveInfo info, boolean preflight) { final BroadcastOptions brOptions = r.options; final ComponentName component = new ComponentName( info.activityInfo.applicationInfo.packageName, @@ -134,15 +144,23 @@ public class BroadcastSkipPolicy { + " requires " + info.activityInfo.permission; } } else if (info.activityInfo.permission != null) { - final int opCode = AppOpsManager.permissionToOpCode(info.activityInfo.permission); - if (opCode != AppOpsManager.OP_NONE && mService.getAppOpsManager().noteOpNoThrow(opCode, - r.callingUid, r.callerPackage, r.callerFeatureId, - "Broadcast delivered to " + info.activityInfo.name) - != AppOpsManager.MODE_ALLOWED) { - return "Appop Denial: broadcasting " - + broadcastDescription(r, component) - + " requires appop " + AppOpsManager.permissionToOp( - info.activityInfo.permission); + final String op = AppOpsManager.permissionToOp(info.activityInfo.permission); + if (op != null) { + final int mode; + if (preflight) { + mode = mService.getAppOpsManager().checkOpNoThrow(op, + r.callingUid, r.callerPackage, r.callerFeatureId); + } else { + mode = mService.getAppOpsManager().noteOpNoThrow(op, + r.callingUid, r.callerPackage, r.callerFeatureId, + "Broadcast delivered to " + info.activityInfo.name); + } + if (mode != AppOpsManager.MODE_ALLOWED) { + return "Appop Denial: broadcasting " + + broadcastDescription(r, component) + + " requires appop " + AppOpsManager.permissionToOp( + info.activityInfo.permission); + } } } @@ -250,8 +268,8 @@ public class BroadcastSkipPolicy { perm = PackageManager.PERMISSION_DENIED; } - int appOp = AppOpsManager.permissionToOpCode(excludedPermission); - if (appOp != AppOpsManager.OP_NONE) { + final String appOp = AppOpsManager.permissionToOp(excludedPermission); + if (appOp != null) { // When there is an app op associated with the permission, // skip when both the permission and the app op are // granted. @@ -259,7 +277,7 @@ public class BroadcastSkipPolicy { mService.getAppOpsManager().checkOpNoThrow(appOp, info.activityInfo.applicationInfo.uid, info.activityInfo.packageName) - == AppOpsManager.MODE_ALLOWED)) { + == AppOpsManager.MODE_ALLOWED)) { return "Skipping delivery to " + info.activityInfo.packageName + " due to excluded permission " + excludedPermission; } @@ -292,9 +310,10 @@ public class BroadcastSkipPolicy { createAttributionSourcesForResolveInfo(info); for (int i = 0; i < r.requiredPermissions.length; i++) { String requiredPermission = r.requiredPermissions[i]; - perm = hasPermissionForDataDelivery( + perm = hasPermission( requiredPermission, "Broadcast delivered to " + info.activityInfo.name, + preflight, attributionSources) ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED; @@ -308,10 +327,14 @@ public class BroadcastSkipPolicy { } } } - if (r.appOp != AppOpsManager.OP_NONE) { - if (!noteOpForManifestReceiver(r.appOp, r, info, component)) { + if (r.appOp != AppOpsManager.OP_NONE && AppOpsManager.isValidOp(r.appOp)) { + final String op = AppOpsManager.opToPublicName(r.appOp); + final boolean appOpAllowed = preflight + ? checkOpForManifestReceiver(r.appOp, op, r, info, component) + : noteOpForManifestReceiver(r.appOp, op, r, info, component); + if (!appOpAllowed) { return "Skipping delivery to " + info.activityInfo.packageName - + " due to required appop " + r.appOp; + + " due to required appop " + AppOpsManager.opToName(r.appOp); } } @@ -338,7 +361,7 @@ public class BroadcastSkipPolicy { * {@code null} if it can proceed. */ private @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r, - @NonNull BroadcastFilter filter) { + @NonNull BroadcastFilter filter, boolean preflight) { if (r.options != null && !r.options.testRequireCompatChange(filter.owningUid)) { return "Compat change filtered: broadcasting " + r.intent.toString() + " to uid " + filter.owningUid + " due to compat change " @@ -372,18 +395,25 @@ public class BroadcastSkipPolicy { + " requires " + filter.requiredPermission + " due to registered receiver " + filter; } else { - final int opCode = AppOpsManager.permissionToOpCode(filter.requiredPermission); - if (opCode != AppOpsManager.OP_NONE - && mService.getAppOpsManager().noteOpNoThrow(opCode, r.callingUid, - r.callerPackage, r.callerFeatureId, "Broadcast sent to protected receiver") - != AppOpsManager.MODE_ALLOWED) { - return "Appop Denial: broadcasting " - + r.intent.toString() - + " from " + r.callerPackage + " (pid=" - + r.callingPid + ", uid=" + r.callingUid + ")" - + " requires appop " + AppOpsManager.permissionToOp( - filter.requiredPermission) - + " due to registered receiver " + filter; + final String op = AppOpsManager.permissionToOp(filter.requiredPermission); + if (op != null) { + final int mode; + if (preflight) { + mode = mService.getAppOpsManager().checkOpNoThrow(op, + r.callingUid, r.callerPackage, r.callerFeatureId); + } else { + mode = mService.getAppOpsManager().noteOpNoThrow(op, r.callingUid, + r.callerPackage, r.callerFeatureId, + "Broadcast sent to protected receiver"); + } + if (mode != AppOpsManager.MODE_ALLOWED) { + return "Appop Denial: broadcasting " + + r.intent + + " from " + r.callerPackage + " (pid=" + + r.callingPid + ", uid=" + r.callingUid + ")" + + " requires appop " + op + + " due to registered receiver " + filter; + } } } } @@ -433,9 +463,10 @@ public class BroadcastSkipPolicy { .build(); for (int i = 0; i < r.requiredPermissions.length; i++) { String requiredPermission = r.requiredPermissions[i]; - final int perm = hasPermissionForDataDelivery( + final int perm = hasPermission( requiredPermission, "Broadcast delivered to registered receiver " + filter.receiverId, + preflight, attributionSource) ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED; @@ -471,8 +502,8 @@ public class BroadcastSkipPolicy { final int perm = checkComponentPermission(excludedPermission, filter.receiverList.pid, filter.receiverList.uid, -1, true); - int appOp = AppOpsManager.permissionToOpCode(excludedPermission); - if (appOp != AppOpsManager.OP_NONE) { + final String appOp = AppOpsManager.permissionToOp(excludedPermission); + if (appOp != null) { // When there is an app op associated with the permission, // skip when both the permission and the app op are // granted. @@ -480,14 +511,13 @@ public class BroadcastSkipPolicy { mService.getAppOpsManager().checkOpNoThrow(appOp, filter.receiverList.uid, filter.packageName) - == AppOpsManager.MODE_ALLOWED)) { + == AppOpsManager.MODE_ALLOWED)) { return "Appop Denial: receiving " - + r.intent.toString() + + r.intent + " to " + filter.receiverList.app + " (pid=" + filter.receiverList.pid + ", uid=" + filter.receiverList.uid + ")" - + " excludes appop " + AppOpsManager.permissionToOp( - excludedPermission) + + " excludes appop " + appOp + " due to sender " + r.callerPackage + " (uid " + r.callingUid + ")"; } @@ -496,7 +526,7 @@ public class BroadcastSkipPolicy { // skip when permission is granted. if (perm == PackageManager.PERMISSION_GRANTED) { return "Permission Denial: receiving " - + r.intent.toString() + + r.intent + " to " + filter.receiverList.app + " (pid=" + filter.receiverList.pid + ", uid=" + filter.receiverList.uid + ")" @@ -523,19 +553,27 @@ public class BroadcastSkipPolicy { } // If the broadcast also requires an app op check that as well. - if (r.appOp != AppOpsManager.OP_NONE - && mService.getAppOpsManager().noteOpNoThrow(r.appOp, - filter.receiverList.uid, filter.packageName, filter.featureId, - "Broadcast delivered to registered receiver " + filter.receiverId) - != AppOpsManager.MODE_ALLOWED) { - return "Appop Denial: receiving " - + r.intent.toString() - + " to " + filter.receiverList.app - + " (pid=" + filter.receiverList.pid - + ", uid=" + filter.receiverList.uid + ")" - + " requires appop " + AppOpsManager.opToName(r.appOp) - + " due to sender " + r.callerPackage - + " (uid " + r.callingUid + ")"; + if (r.appOp != AppOpsManager.OP_NONE && AppOpsManager.isValidOp(r.appOp)) { + final String op = AppOpsManager.opToPublicName(r.appOp); + final int mode; + if (preflight) { + mode = mService.getAppOpsManager().checkOpNoThrow(op, + filter.receiverList.uid, filter.packageName, filter.featureId); + } else { + mode = mService.getAppOpsManager().noteOpNoThrow(op, + filter.receiverList.uid, filter.packageName, filter.featureId, + "Broadcast delivered to registered receiver " + filter.receiverId); + } + if (mode != AppOpsManager.MODE_ALLOWED) { + return "Appop Denial: receiving " + + r.intent + + " to " + filter.receiverList.app + + " (pid=" + filter.receiverList.pid + + ", uid=" + filter.receiverList.uid + ")" + + " requires appop " + AppOpsManager.opToName(r.appOp) + + " due to sender " + r.callerPackage + + " (uid " + r.callingUid + ")"; + } } // Ensure that broadcasts are only sent to other apps if they are explicitly marked as @@ -572,14 +610,14 @@ public class BroadcastSkipPolicy { + ", uid=" + r.callingUid + ") to " + component.flattenToShortString(); } - private boolean noteOpForManifestReceiver(int appOp, BroadcastRecord r, ResolveInfo info, - ComponentName component) { + private boolean noteOpForManifestReceiver(int opCode, String appOp, BroadcastRecord r, + ResolveInfo info, ComponentName component) { if (ArrayUtils.isEmpty(info.activityInfo.attributionTags)) { - return noteOpForManifestReceiverInner(appOp, r, info, component, null); + return noteOpForManifestReceiverInner(opCode, appOp, r, info, component, null); } else { // Attribution tags provided, noteOp each tag for (String tag : info.activityInfo.attributionTags) { - if (!noteOpForManifestReceiverInner(appOp, r, info, component, tag)) { + if (!noteOpForManifestReceiverInner(opCode, appOp, r, info, component, tag)) { return false; } } @@ -587,8 +625,8 @@ public class BroadcastSkipPolicy { } } - private boolean noteOpForManifestReceiverInner(int appOp, BroadcastRecord r, ResolveInfo info, - ComponentName component, String tag) { + private boolean noteOpForManifestReceiverInner(int opCode, String appOp, BroadcastRecord r, + ResolveInfo info, ComponentName component, String tag) { if (mService.getAppOpsManager().noteOpNoThrow(appOp, info.activityInfo.applicationInfo.uid, info.activityInfo.packageName, @@ -598,7 +636,37 @@ public class BroadcastSkipPolicy { Slog.w(TAG, "Appop Denial: receiving " + r.intent + " to " + component.flattenToShortString() - + " requires appop " + AppOpsManager.opToName(appOp) + + " requires appop " + AppOpsManager.opToName(opCode) + + " due to sender " + r.callerPackage + + " (uid " + r.callingUid + ")"); + return false; + } + return true; + } + + private boolean checkOpForManifestReceiver(int opCode, String appOp, BroadcastRecord r, + ResolveInfo info, ComponentName component) { + if (ArrayUtils.isEmpty(info.activityInfo.attributionTags)) { + return checkOpForManifestReceiverInner(opCode, appOp, r, info, component, null); + } else { + // Attribution tags provided, noteOp each tag + for (String tag : info.activityInfo.attributionTags) { + if (!checkOpForManifestReceiverInner(opCode, appOp, r, info, component, tag)) { + return false; + } + } + return true; + } + } + + private boolean checkOpForManifestReceiverInner(int opCode, String appOp, BroadcastRecord r, + ResolveInfo info, ComponentName component, String tag) { + if (mService.getAppOpsManager().checkOpNoThrow(appOp, info.activityInfo.applicationInfo.uid, + info.activityInfo.packageName, tag) != AppOpsManager.MODE_ALLOWED) { + Slog.w(TAG, "Appop Denial: receiving " + + r.intent + " to " + + component.flattenToShortString() + + " requires appop " + AppOpsManager.opToName(opCode) + " due to sender " + r.callerPackage + " (uid " + r.callingUid + ")"); return false; @@ -694,9 +762,10 @@ public class BroadcastSkipPolicy { return mPermissionManager; } - private boolean hasPermissionForDataDelivery( + private boolean hasPermission( @NonNull String permission, @NonNull String message, + boolean preflight, @NonNull AttributionSource... attributionSources) { final PermissionManager permissionManager = getPermissionManager(); if (permissionManager == null) { @@ -704,9 +773,14 @@ public class BroadcastSkipPolicy { } for (AttributionSource attributionSource : attributionSources) { - final int permissionCheckResult = - permissionManager.checkPermissionForDataDelivery( - permission, attributionSource, message); + final int permissionCheckResult; + if (preflight) { + permissionCheckResult = permissionManager.checkPermissionForPreflight( + permission, attributionSource); + } else { + permissionCheckResult = permissionManager.checkPermissionForDataDelivery( + permission, attributionSource, message); + } if (permissionCheckResult != PackageManager.PERMISSION_GRANTED) { return false; } diff --git a/services/core/java/com/android/server/am/broadcasts_flags.aconfig b/services/core/java/com/android/server/am/broadcasts_flags.aconfig index 7f169db7dcec..68e21a35a531 100644 --- a/services/core/java/com/android/server/am/broadcasts_flags.aconfig +++ b/services/core/java/com/android/server/am/broadcasts_flags.aconfig @@ -15,4 +15,15 @@ flag { description: "Limit the scope of receiver priorities to within a process" is_fixed_read_only: true bug: "369487976" +} + +flag { + name: "avoid_note_op_at_enqueue" + namespace: "backstage_power" + description: "Avoid triggering noteOp while enqueueing a broadcast" + is_fixed_read_only: true + bug: "268016162" + metadata { + purpose: PURPOSE_BUGFIX + } }
\ No newline at end of file diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java index 5eb23a24908d..12866481b320 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java @@ -16,29 +16,43 @@ package com.android.server.am; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import android.annotation.NonNull; +import android.app.Activity; +import android.app.ActivityManager; +import android.app.AppGlobals; +import android.app.AppOpsManager; +import android.app.BackgroundStartPrivileges; +import android.app.BroadcastOptions; +import android.app.SystemServiceRegistry; import android.app.usage.UsageStatsManagerInternal; import android.content.ComponentName; import android.content.Context; +import android.content.IIntentReceiver; +import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; +import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.TestLooperManager; import android.os.UserHandle; +import android.permission.IPermissionManager; +import android.permission.PermissionManager; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.SetFlagsRule; @@ -47,7 +61,6 @@ import android.util.SparseArray; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.util.FrameworkStatsLog; import com.android.modules.utils.testing.ExtendedMockitoRule; import com.android.server.AlarmManagerInternal; @@ -55,6 +68,7 @@ import com.android.server.DropBoxManagerInternal; import com.android.server.LocalServices; import com.android.server.appop.AppOpsService; import com.android.server.compat.PlatformCompat; +import com.android.server.firewall.IntentFirewall; import com.android.server.wm.ActivityTaskManagerService; import org.junit.Rule; @@ -63,8 +77,11 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.io.File; +import java.util.Collections; +import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; public abstract class BaseBroadcastQueueTest { @@ -97,6 +114,8 @@ public abstract class BaseBroadcastQueueTest { public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this) .spyStatic(FrameworkStatsLog.class) .spyStatic(ProcessList.class) + .spyStatic(SystemServiceRegistry.class) + .mockStatic(AppGlobals.class) .build(); @@ -119,6 +138,16 @@ public abstract class BaseBroadcastQueueTest { ProcessList mProcessList; @Mock PlatformCompat mPlatformCompat; + @Mock + IntentFirewall mIntentFirewall; + @Mock + IPackageManager mIPackageManager; + @Mock + AppOpsManager mAppOpsManager; + @Mock + IPermissionManager mIPermissionManager; + @Mock + PermissionManager mPermissionManager; @Mock AppStartInfoTracker mAppStartInfoTracker; @@ -167,22 +196,22 @@ public abstract class BaseBroadcastQueueTest { return getUidForPackage(invocation.getArgument(0)); }).when(mPackageManagerInt).getPackageUid(any(), anyLong(), eq(UserHandle.USER_SYSTEM)); + final Context spyContext = spy(mContext); + doReturn(mPermissionManager).when(spyContext).getSystemService(PermissionManager.class); final ActivityManagerService realAms = new ActivityManagerService( - new TestInjector(mContext), mServiceThreadRule.getThread()); + new TestInjector(spyContext), mServiceThreadRule.getThread()); realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext); realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper()); realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal()); realAms.mOomAdjuster.mCachedAppOptimizer = mock(CachedAppOptimizer.class); realAms.mOomAdjuster = spy(realAms.mOomAdjuster); - ExtendedMockito.doNothing().when(() -> ProcessList.setOomAdj(anyInt(), anyInt(), anyInt())); + doNothing().when(() -> ProcessList.setOomAdj(anyInt(), anyInt(), anyInt())); realAms.mPackageManagerInt = mPackageManagerInt; realAms.mUsageStatsService = mUsageStatsManagerInt; realAms.mProcessesReady = true; mAms = spy(realAms); - mSkipPolicy = spy(new BroadcastSkipPolicy(mAms)); - doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any()); - doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any()); + mSkipPolicy = createBroadcastSkipPolicy(); doReturn(mAppStartInfoTracker).when(mProcessList).getAppStartInfoTracker(); @@ -198,6 +227,14 @@ public abstract class BaseBroadcastQueueTest { } } + public BroadcastSkipPolicy createBroadcastSkipPolicy() { + final BroadcastSkipPolicy skipPolicy = spy(new BroadcastSkipPolicy(mAms)); + doReturn(null).when(skipPolicy).shouldSkipAtEnqueueMessage(any(), any()); + doReturn(null).when(skipPolicy).shouldSkipMessage(any(), any()); + doReturn(false).when(skipPolicy).disallowBackgroundStart(any()); + return skipPolicy; + } + static int getUidForPackage(@NonNull String packageName) { switch (packageName) { case PACKAGE_ANDROID: return android.os.Process.SYSTEM_UID; @@ -240,6 +277,11 @@ public abstract class BaseBroadcastQueueTest { public BroadcastQueue getBroadcastQueue(ActivityManagerService service) { return null; } + + @Override + public IntentFirewall getIntentFirewall() { + return mIntentFirewall; + } } abstract String getTag(); @@ -281,24 +323,35 @@ public abstract class BaseBroadcastQueueTest { ri.activityInfo.packageName = packageName; ri.activityInfo.processName = processName; ri.activityInfo.name = name; + ri.activityInfo.exported = true; ri.activityInfo.applicationInfo = makeApplicationInfo(packageName, processName, userId); return ri; } + // TODO: Reuse BroadcastQueueTest.makeActiveProcessRecord() + @SuppressWarnings("GuardedBy") + ProcessRecord makeProcessRecord(ApplicationInfo info) { + final ProcessRecord r = spy(new ProcessRecord(mAms, info, info.processName, info.uid)); + r.setPid(mNextPid.incrementAndGet()); + ProcessRecord.updateProcessRecordNodes(r); + return r; + } + BroadcastFilter makeRegisteredReceiver(ProcessRecord app) { return makeRegisteredReceiver(app, 0); } BroadcastFilter makeRegisteredReceiver(ProcessRecord app, int priority) { final ReceiverList receiverList = mRegisteredReceivers.get(app.getPid()); - return makeRegisteredReceiver(receiverList, priority); + return makeRegisteredReceiver(receiverList, priority, null); } - static BroadcastFilter makeRegisteredReceiver(ReceiverList receiverList, int priority) { + static BroadcastFilter makeRegisteredReceiver(ReceiverList receiverList, int priority, + String requiredPermission) { final IntentFilter filter = new IntentFilter(); filter.setPriority(priority); final BroadcastFilter res = new BroadcastFilter(filter, receiverList, - receiverList.app.info.packageName, null, null, null, receiverList.uid, + receiverList.app.info.packageName, null, null, requiredPermission, receiverList.uid, receiverList.userId, false, false, true, receiverList.app.info, mock(PlatformCompat.class)); receiverList.add(res); @@ -313,4 +366,62 @@ public abstract class BaseBroadcastQueueTest { ArgumentMatcher<ApplicationInfo> appInfoEquals(int uid) { return test -> (test.uid == uid); } + + static final class BroadcastRecordBuilder { + private BroadcastQueue mQueue = mock(BroadcastQueue.class); + private Intent mIntent = mock(Intent.class); + private ProcessRecord mProcessRecord = mock(ProcessRecord.class); + private String mCallerPackage; + private String mCallerFeatureId; + private int mCallingPid; + private int mCallingUid; + private boolean mCallerInstantApp; + private String mResolvedType; + private String[] mRequiredPermissions; + private String[] mExcludedPermissions; + private String[] mExcludedPackages; + private int mAppOp; + private BroadcastOptions mOptions = BroadcastOptions.makeBasic(); + private List mReceivers = Collections.emptyList(); + private ProcessRecord mResultToApp; + private IIntentReceiver mResultTo; + private int mResultCode = Activity.RESULT_OK; + private String mResultData; + private Bundle mResultExtras; + private boolean mSerialized; + private boolean mSticky; + private boolean mInitialSticky; + private int mUserId = UserHandle.USER_SYSTEM; + private BackgroundStartPrivileges mBackgroundStartPrivileges = + BackgroundStartPrivileges.NONE; + private boolean mTimeoutExempt; + private BiFunction<Integer, Bundle, Bundle> mFilterExtrasForReceiver; + private int mCallerAppProcState = ActivityManager.PROCESS_STATE_UNKNOWN; + private PlatformCompat mPlatformCompat = mock(PlatformCompat.class); + + public BroadcastRecordBuilder setIntent(Intent intent) { + mIntent = intent; + return this; + } + + public BroadcastRecordBuilder setRequiredPermissions(String[] requiredPermissions) { + mRequiredPermissions = requiredPermissions; + return this; + } + + public BroadcastRecordBuilder setAppOp(int appOp) { + mAppOp = appOp; + return this; + } + + public BroadcastRecord build() { + return new BroadcastRecord(mQueue, mIntent, mProcessRecord, mCallerPackage, + mCallerFeatureId, mCallingPid, mCallingUid, mCallerInstantApp, mResolvedType, + mRequiredPermissions, mExcludedPermissions, mExcludedPackages, mAppOp, + mOptions, mReceivers, mResultToApp, mResultTo, mResultCode, mResultData, + mResultExtras, mSerialized, mSticky, mInitialSticky, mUserId, + mBackgroundStartPrivileges, mTimeoutExempt, mFilterExtrasForReceiver, + mCallerAppProcState, mPlatformCompat); + } + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java index 409706b14c56..b32ce49d049d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java @@ -1803,8 +1803,10 @@ public final class BroadcastQueueImplTest extends BaseBroadcastQueueTest { assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked()); } + @SuppressWarnings("GuardedBy") + @DisableFlags(Flags.FLAG_AVOID_NOTE_OP_AT_ENQUEUE) @Test - public void testSkipPolicy_atEnqueueTime() throws Exception { + public void testSkipPolicy_atEnqueueTime_flagDisabled() throws Exception { final Intent userPresent = new Intent(Intent.ACTION_USER_PRESENT); final Object greenReceiver = makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN); final Object redReceiver = makeManifestReceiver(PACKAGE_RED, CLASS_RED); @@ -1839,6 +1841,44 @@ public final class BroadcastQueueImplTest extends BaseBroadcastQueueTest { verifyPendingRecords(redQueue, List.of(userPresent, timeTick)); } + @SuppressWarnings("GuardedBy") + @EnableFlags(Flags.FLAG_AVOID_NOTE_OP_AT_ENQUEUE) + @Test + public void testSkipPolicy_atEnqueueTime() throws Exception { + final Intent userPresent = new Intent(Intent.ACTION_USER_PRESENT); + final Object greenReceiver = makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN); + final Object redReceiver = makeManifestReceiver(PACKAGE_RED, CLASS_RED); + + final BroadcastRecord userPresentRecord = makeBroadcastRecord(userPresent, + List.of(greenReceiver, redReceiver)); + + final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK); + final BroadcastRecord timeTickRecord = makeBroadcastRecord(timeTick, + List.of(greenReceiver, redReceiver)); + + doAnswer(invocation -> { + final BroadcastRecord r = invocation.getArgument(0); + final Object o = invocation.getArgument(1); + if (userPresent.getAction().equals(r.intent.getAction()) + && isReceiverEquals(o, greenReceiver)) { + return "receiver skipped by test"; + } + return null; + }).when(mSkipPolicy).shouldSkipAtEnqueueMessage(any(BroadcastRecord.class), any()); + + mImpl.enqueueBroadcastLocked(userPresentRecord); + mImpl.enqueueBroadcastLocked(timeTickRecord); + + final BroadcastProcessQueue greenQueue = mImpl.getProcessQueue(PACKAGE_GREEN, + getUidForPackage(PACKAGE_GREEN)); + // There should be only one broadcast for green process as the other would have + // been skipped. + verifyPendingRecords(greenQueue, List.of(timeTick)); + final BroadcastProcessQueue redQueue = mImpl.getProcessQueue(PACKAGE_RED, + getUidForPackage(PACKAGE_RED)); + verifyPendingRecords(redQueue, List.of(userPresent, timeTick)); + } + @DisableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE) @Test public void testDeliveryDeferredForCached_flagDisabled() throws Exception { @@ -2270,19 +2310,11 @@ public final class BroadcastQueueImplTest extends BaseBroadcastQueueTest { assertFalse(mImpl.isProcessFreezable(greenProcess)); } - // TODO: Reuse BroadcastQueueTest.makeActiveProcessRecord() - private ProcessRecord makeProcessRecord(ApplicationInfo info) { - final ProcessRecord r = spy(new ProcessRecord(mAms, info, info.processName, info.uid)); - r.setPid(mNextPid.incrementAndGet()); - ProcessRecord.updateProcessRecordNodes(r); - return r; - } - BroadcastFilter makeRegisteredReceiver(ProcessRecord app, int priority) { final IIntentReceiver receiver = mock(IIntentReceiver.class); final ReceiverList receiverList = new ReceiverList(mAms, app, app.getPid(), app.info.uid, UserHandle.getUserId(app.info.uid), receiver); - return makeRegisteredReceiver(receiverList, priority); + return makeRegisteredReceiver(receiverList, priority, null /* requiredPermission */); } private Intent createPackageChangedIntent(int uid, List<String> componentNameList) { diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index ad35b25a0d74..3a9c99d57d71 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -2301,6 +2301,52 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { } /** + * Verify that we skip broadcasts at enqueue if {@link BroadcastSkipPolicy} decides it + * should be skipped. + */ + @EnableFlags(Flags.FLAG_AVOID_NOTE_OP_AT_ENQUEUE) + @Test + public void testSkipPolicy_atEnqueueTime() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN); + final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE); + + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + final Object greenReceiver = makeRegisteredReceiver(receiverGreenApp); + final Object blueReceiver = makeRegisteredReceiver(receiverBlueApp); + final Object yellowReceiver = makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW); + final Object orangeReceiver = makeManifestReceiver(PACKAGE_ORANGE, CLASS_ORANGE); + + doAnswer(invocation -> { + final BroadcastRecord r = invocation.getArgument(0); + final Object o = invocation.getArgument(1); + if (airplane.getAction().equals(r.intent.getAction()) + && (isReceiverEquals(o, greenReceiver) + || isReceiverEquals(o, orangeReceiver))) { + return "test skipped receiver"; + } + return null; + }).when(mSkipPolicy).shouldSkipAtEnqueueMessage(any(BroadcastRecord.class), any()); + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, + List.of(greenReceiver, blueReceiver, yellowReceiver, orangeReceiver))); + + waitForIdle(); + // Verify that only blue and yellow receiver apps received the broadcast. + verifyScheduleRegisteredReceiver(never(), receiverGreenApp, USER_SYSTEM); + verify(mSkipPolicy, never()).shouldSkipMessage(any(BroadcastRecord.class), + eq(greenReceiver)); + verifyScheduleRegisteredReceiver(receiverBlueApp, airplane); + final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW, + getUidForPackage(PACKAGE_YELLOW)); + verifyScheduleReceiver(receiverYellowApp, airplane); + final ProcessRecord receiverOrangeApp = mAms.getProcessRecordLocked(PACKAGE_ORANGE, + getUidForPackage(PACKAGE_ORANGE)); + assertNull(receiverOrangeApp); + verify(mSkipPolicy, never()).shouldSkipMessage(any(BroadcastRecord.class), + eq(orangeReceiver)); + } + + /** * Verify broadcasts to runtime receivers in cached processes are deferred * until that process leaves the cached state. */ diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastSkipPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastSkipPolicyTest.java new file mode 100644 index 000000000000..c8aad79edd12 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastSkipPolicyTest.java @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2025 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 com.android.server.am; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.never; + +import android.Manifest; +import android.app.ActivityManager; +import android.app.AppGlobals; +import android.app.AppOpsManager; +import android.content.IIntentReceiver; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.UserHandle; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; + +@SmallTest +public class BroadcastSkipPolicyTest extends BaseBroadcastQueueTest { + private static final String TAG = "BroadcastSkipPolicyTest"; + + BroadcastSkipPolicy mBroadcastSkipPolicy; + + @Before + public void setUp() throws Exception { + super.setUp(); + mBroadcastSkipPolicy = new BroadcastSkipPolicy(mAms); + + doReturn(true).when(mIntentFirewall).checkBroadcast(any(Intent.class), + anyInt(), anyInt(), nullable(String.class), anyInt()); + + doReturn(mIPackageManager).when(AppGlobals::getPackageManager); + doReturn(true).when(mIPackageManager).isPackageAvailable(anyString(), anyInt()); + + doReturn(ActivityManager.APP_START_MODE_NORMAL).when(mAms).getAppStartModeLOSP(anyInt(), + anyString(), anyInt(), anyInt(), eq(true), eq(false), eq(false)); + + doReturn(mAppOpsManager).when(mAms).getAppOpsManager(); + doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager).checkOpNoThrow(anyString(), + anyInt(), anyString(), nullable(String.class)); + doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager).noteOpNoThrow(anyString(), + anyInt(), anyString(), nullable(String.class), anyString()); + + doReturn(mIPermissionManager).when(AppGlobals::getPermissionManager); + doReturn(PackageManager.PERMISSION_GRANTED).when(mIPermissionManager).checkUidPermission( + anyInt(), anyString(), anyInt()); + } + + @Override + public String getTag() { + return TAG; + } + + @Override + public BroadcastSkipPolicy createBroadcastSkipPolicy() { + return new BroadcastSkipPolicy(mAms); + } + + @Test + public void testShouldSkipMessage_withManifestRcvr_withCompPerm_invokesNoteOp() { + final BroadcastRecord record = new BroadcastRecordBuilder() + .setIntent(new Intent(Intent.ACTION_TIME_TICK)) + .build(); + final String msg = mBroadcastSkipPolicy.shouldSkipMessage(record, + makeManifestReceiverWithPermission(PACKAGE_GREEN, CLASS_GREEN, + Manifest.permission.PACKAGE_USAGE_STATS)); + assertNull(msg); + verify(mAppOpsManager).noteOpNoThrow( + eq(AppOpsManager.permissionToOp(Manifest.permission.PACKAGE_USAGE_STATS)), + eq(record.callingUid), eq(record.callerPackage), eq(record.callerFeatureId), + anyString()); + verify(mAppOpsManager, never()).checkOpNoThrow( + anyString(), anyInt(), anyString(), nullable(String.class)); + } + + @Test + public void testShouldSkipMessage_withRegRcvr_withCompPerm_invokesNoteOp() { + final BroadcastRecord record = new BroadcastRecordBuilder() + .setIntent(new Intent(Intent.ACTION_TIME_TICK)) + .build(); + final ProcessRecord receiverApp = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN)); + final String msg = mBroadcastSkipPolicy.shouldSkipMessage(record, + makeRegisteredReceiver(receiverApp, 0 /* priority */, + Manifest.permission.PACKAGE_USAGE_STATS)); + assertNull(msg); + verify(mAppOpsManager).noteOpNoThrow( + eq(AppOpsManager.permissionToOp(Manifest.permission.PACKAGE_USAGE_STATS)), + eq(record.callingUid), eq(record.callerPackage), eq(record.callerFeatureId), + anyString()); + verify(mAppOpsManager, never()).checkOpNoThrow( + anyString(), anyInt(), anyString(), nullable(String.class)); + } + + @Test + public void testShouldSkipAtEnqueueMessage_withManifestRcvr_withCompPerm_invokesCheckOp() { + final BroadcastRecord record = new BroadcastRecordBuilder() + .setIntent(new Intent(Intent.ACTION_TIME_TICK)) + .build(); + final String msg = mBroadcastSkipPolicy.shouldSkipAtEnqueueMessage(record, + makeManifestReceiverWithPermission(PACKAGE_GREEN, CLASS_GREEN, + Manifest.permission.PACKAGE_USAGE_STATS)); + assertNull(msg); + verify(mAppOpsManager).checkOpNoThrow( + eq(AppOpsManager.permissionToOp(Manifest.permission.PACKAGE_USAGE_STATS)), + eq(record.callingUid), eq(record.callerPackage), eq(record.callerFeatureId)); + verify(mAppOpsManager, never()).noteOpNoThrow( + anyString(), anyInt(), anyString(), nullable(String.class), anyString()); + } + + @Test + public void testShouldSkipAtEnqueueMessage_withRegRcvr_withCompPerm_invokesCheckOp() { + final BroadcastRecord record = new BroadcastRecordBuilder() + .setIntent(new Intent(Intent.ACTION_TIME_TICK)) + .build(); + final ProcessRecord receiverApp = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN)); + final String msg = mBroadcastSkipPolicy.shouldSkipAtEnqueueMessage(record, + makeRegisteredReceiver(receiverApp, 0 /* priority */, + Manifest.permission.PACKAGE_USAGE_STATS)); + assertNull(msg); + verify(mAppOpsManager).checkOpNoThrow( + eq(AppOpsManager.permissionToOp(Manifest.permission.PACKAGE_USAGE_STATS)), + eq(record.callingUid), eq(record.callerPackage), eq(record.callerFeatureId)); + verify(mAppOpsManager, never()).noteOpNoThrow( + anyString(), anyInt(), anyString(), nullable(String.class), anyString()); + } + + @Test + public void testShouldSkipMessage_withManifestRcvr_withAppOp_invokesNoteOp() { + final BroadcastRecord record = new BroadcastRecordBuilder() + .setIntent(new Intent(Intent.ACTION_TIME_TICK)) + .setAppOp(AppOpsManager.permissionToOpCode(Manifest.permission.PACKAGE_USAGE_STATS)) + .build(); + final ResolveInfo receiver = makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN); + final String msg = mBroadcastSkipPolicy.shouldSkipMessage(record, receiver); + assertNull(msg); + verify(mAppOpsManager).noteOpNoThrow( + eq(AppOpsManager.permissionToOp(Manifest.permission.PACKAGE_USAGE_STATS)), + eq(receiver.activityInfo.applicationInfo.uid), + eq(receiver.activityInfo.packageName), nullable(String.class), anyString()); + verify(mAppOpsManager, never()).checkOpNoThrow( + anyString(), anyInt(), anyString(), nullable(String.class)); + } + + @Test + public void testShouldSkipMessage_withRegRcvr_withAppOp_invokesNoteOp() { + final BroadcastRecord record = new BroadcastRecordBuilder() + .setIntent(new Intent(Intent.ACTION_TIME_TICK)) + .setAppOp(AppOpsManager.permissionToOpCode(Manifest.permission.PACKAGE_USAGE_STATS)) + .build(); + final ProcessRecord receiverApp = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN)); + final BroadcastFilter filter = makeRegisteredReceiver(receiverApp, 0 /* priority */, + null /* requiredPermission */); + final String msg = mBroadcastSkipPolicy.shouldSkipMessage(record, filter); + assertNull(msg); + verify(mAppOpsManager).noteOpNoThrow( + eq(AppOpsManager.permissionToOp(Manifest.permission.PACKAGE_USAGE_STATS)), + eq(filter.receiverList.uid), + eq(filter.packageName), nullable(String.class), anyString()); + verify(mAppOpsManager, never()).checkOpNoThrow( + anyString(), anyInt(), anyString(), nullable(String.class)); + } + + @Test + public void testShouldSkipAtEnqueueMessage_withManifestRcvr_withAppOp_invokesCheckOp() { + final BroadcastRecord record = new BroadcastRecordBuilder() + .setIntent(new Intent(Intent.ACTION_TIME_TICK)) + .setAppOp(AppOpsManager.permissionToOpCode(Manifest.permission.PACKAGE_USAGE_STATS)) + .build(); + final ResolveInfo receiver = makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN); + final String msg = mBroadcastSkipPolicy.shouldSkipAtEnqueueMessage(record, receiver); + assertNull(msg); + verify(mAppOpsManager).checkOpNoThrow( + eq(AppOpsManager.permissionToOp(Manifest.permission.PACKAGE_USAGE_STATS)), + eq(receiver.activityInfo.applicationInfo.uid), + eq(receiver.activityInfo.applicationInfo.packageName), nullable(String.class)); + verify(mAppOpsManager, never()).noteOpNoThrow( + anyString(), anyInt(), anyString(), nullable(String.class), anyString()); + } + + @Test + public void testShouldSkipAtEnqueueMessage_withRegRcvr_withAppOp_invokesCheckOp() { + final BroadcastRecord record = new BroadcastRecordBuilder() + .setIntent(new Intent(Intent.ACTION_TIME_TICK)) + .setAppOp(AppOpsManager.permissionToOpCode(Manifest.permission.PACKAGE_USAGE_STATS)) + .build(); + final ProcessRecord receiverApp = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN)); + final BroadcastFilter filter = makeRegisteredReceiver(receiverApp, 0 /* priority */, + null /* requiredPermission */); + final String msg = mBroadcastSkipPolicy.shouldSkipAtEnqueueMessage(record, filter); + assertNull(msg); + verify(mAppOpsManager).checkOpNoThrow( + eq(AppOpsManager.permissionToOp(Manifest.permission.PACKAGE_USAGE_STATS)), + eq(filter.receiverList.uid), + eq(filter.packageName), nullable(String.class)); + verify(mAppOpsManager, never()).noteOpNoThrow( + anyString(), anyInt(), anyString(), nullable(String.class), anyString()); + } + + @Test + public void testShouldSkipMessage_withManifestRcvr_withRequiredPerms_invokesNoteOp() { + final BroadcastRecord record = new BroadcastRecordBuilder() + .setIntent(new Intent(Intent.ACTION_TIME_TICK)) + .setRequiredPermissions(new String[] {Manifest.permission.PACKAGE_USAGE_STATS}) + .build(); + final String msg = mBroadcastSkipPolicy.shouldSkipMessage(record, + makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)); + assertNull(msg); + verify(mPermissionManager).checkPermissionForDataDelivery( + eq(Manifest.permission.PACKAGE_USAGE_STATS), any(), anyString()); + verify(mPermissionManager, never()).checkPermissionForPreflight( + eq(Manifest.permission.PACKAGE_USAGE_STATS), any()); + } + + @Test + public void testShouldSkipMessage_withRegRcvr_withRequiredPerms_invokesNoteOp() { + final BroadcastRecord record = new BroadcastRecordBuilder() + .setIntent(new Intent(Intent.ACTION_TIME_TICK)) + .setRequiredPermissions(new String[] {Manifest.permission.PACKAGE_USAGE_STATS}) + .build(); + final ProcessRecord receiverApp = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN)); + final String msg = mBroadcastSkipPolicy.shouldSkipMessage(record, + makeRegisteredReceiver(receiverApp, 0 /* priority */, + null /* requiredPermission */)); + assertNull(msg); + verify(mPermissionManager).checkPermissionForDataDelivery( + eq(Manifest.permission.PACKAGE_USAGE_STATS), any(), anyString()); + verify(mPermissionManager, never()).checkPermissionForPreflight( + eq(Manifest.permission.PACKAGE_USAGE_STATS), any()); + } + + @Test + public void testShouldSkipAtEnqueueMessage_withManifestRcvr_withRequiredPerms_invokesCheckOp() { + final BroadcastRecord record = new BroadcastRecordBuilder() + .setIntent(new Intent(Intent.ACTION_TIME_TICK)) + .setRequiredPermissions(new String[] {Manifest.permission.PACKAGE_USAGE_STATS}) + .build(); + final String msg = mBroadcastSkipPolicy.shouldSkipAtEnqueueMessage(record, + makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)); + assertNull(msg); + verify(mPermissionManager, never()).checkPermissionForDataDelivery( + eq(Manifest.permission.PACKAGE_USAGE_STATS), any(), anyString()); + verify(mPermissionManager).checkPermissionForPreflight( + eq(Manifest.permission.PACKAGE_USAGE_STATS), any()); + } + + @Test + public void testShouldSkipAtEnqueueMessage_withRegRcvr_withRequiredPerms_invokesCheckOp() { + final BroadcastRecord record = new BroadcastRecordBuilder() + .setIntent(new Intent(Intent.ACTION_TIME_TICK)) + .setRequiredPermissions(new String[] {Manifest.permission.PACKAGE_USAGE_STATS}) + .build(); + final ProcessRecord receiverApp = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN)); + final String msg = mBroadcastSkipPolicy.shouldSkipAtEnqueueMessage(record, + makeRegisteredReceiver(receiverApp, 0 /* priority */, + null /* requiredPermission */)); + assertNull(msg); + verify(mPermissionManager, never()).checkPermissionForDataDelivery( + eq(Manifest.permission.PACKAGE_USAGE_STATS), any(), anyString()); + verify(mPermissionManager).checkPermissionForPreflight( + eq(Manifest.permission.PACKAGE_USAGE_STATS), any()); + } + + private ResolveInfo makeManifestReceiverWithPermission(String packageName, String name, + String permission) { + final ResolveInfo resolveInfo = makeManifestReceiver(packageName, name); + resolveInfo.activityInfo.permission = permission; + return resolveInfo; + } + + private BroadcastFilter makeRegisteredReceiver(ProcessRecord app, int priority, + String requiredPermission) { + final IIntentReceiver receiver = mock(IIntentReceiver.class); + final ReceiverList receiverList = new ReceiverList(mAms, app, app.getPid(), app.info.uid, + UserHandle.getUserId(app.info.uid), receiver); + return makeRegisteredReceiver(receiverList, priority, requiredPermission); + } +} |