summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/AppOpsManager.java9
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueImpl.java4
-rw-r--r--services/core/java/com/android/server/am/BroadcastSkipPolicy.java202
-rw-r--r--services/core/java/com/android/server/am/broadcasts_flags.aconfig11
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java133
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java52
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java46
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastSkipPolicyTest.java305
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);
+ }
+}