Move user/package intent reception out of PermissionMonitor

This is a no-op refactoring that moves the reception of
package and user-related intents in PermissionMonitor to
BroadcastReceiveHelper.
It then dispatches these events to the PermissionMonitor.
This change is necessary to centralize intent receivers
from other subclasses to ConnectivityService in
subsequent modifications.

This reverts the design change introduced in aosp/2183343, which
had PermissionMonitor listening to user change events instead
of ConnectivityService.

Flag: EXEMPT refactor
Test: atest ConnectivityCoverageTests:android.net.connectivity.com.android.server.connectivity.PermissionMonitorTest \
      ConnectivityCoverageTests:android.net.connectivity.com.android.server.ConnectivityServiceTest \
      ConnectivityCoverageTests:android.net.connectivity.com.android.server.connectivity.BroadcastReceiveHelperTest \
      --update-device
Bug: 410731080
Change-Id: Ic79afc18a6e8603ddf8d48a1f78cf8d98f2447ae
This commit is contained in:
Junyu Lai 2025-04-15 15:32:54 +08:00
commit 8b1fe1976b
5 changed files with 584 additions and 204 deletions

View file

@ -362,6 +362,7 @@ import com.android.server.connectivity.ApplicationSelfCertifiedNetworkCapabiliti
import com.android.server.connectivity.AutodestructReference;
import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker;
import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker.AutomaticOnOffKeepalive;
import com.android.server.connectivity.BroadcastReceiveHelper;
import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
import com.android.server.connectivity.ClatCoordinator;
import com.android.server.connectivity.ConnectivityFlags;
@ -439,7 +440,8 @@ import java.util.function.Predicate;
* @hide
*/
@TargetApi(Build.VERSION_CODES.S)
public class ConnectivityService extends IConnectivityManager.Stub {
public class ConnectivityService extends IConnectivityManager.Stub
implements BroadcastReceiveHelper.Delegate {
private static final String TAG = ConnectivityService.class.getSimpleName();
private static final String DIAG_ARG = "--diag";
@ -528,6 +530,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private final PermissionMonitor mPermissionMonitor;
private final BroadcastReceiveHelper mBroadcastReceiveHelper;
@VisibleForTesting
final RequestInfoPerUidCounter mNetworkRequestCounter;
@VisibleForTesting
@ -1942,6 +1946,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
mTrackerHandler = new NetworkStateTrackerHandler(mHandlerThread.getLooper());
mConnectivityDiagnosticsHandler =
new ConnectivityDiagnosticsHandler(mHandlerThread.getLooper());
mBroadcastReceiveHelper = new BroadcastReceiveHelper(mContext, mHandler, this);
mReleasePendingIntentDelayMs = Settings.Secure.getInt(context.getContentResolver(),
ConnectivitySettingsManager.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, 5_000);
@ -2009,22 +2014,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
// Listen for user add/removes to inform PermissionMonitor.
// Should run on mHandler to avoid any races.
final IntentFilter userIntentFilter = new IntentFilter();
userIntentFilter.addAction(Intent.ACTION_USER_ADDED);
userIntentFilter.addAction(Intent.ACTION_USER_REMOVED);
mUserAllContext.registerReceiver(mUserIntentReceiver, userIntentFilter,
null /* broadcastPermission */, mHandler);
// Listen to package add/removes for netd
final IntentFilter packageIntentFilter = new IntentFilter();
packageIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
packageIntentFilter.addDataScheme("package");
mUserAllContext.registerReceiver(mPackageIntentReceiver, packageIntentFilter,
null /* broadcastPermission */, mHandler);
// TODO: Move all intent receivers to the helper class.
mBroadcastReceiveHelper.registerReceivers();
// This is needed for pre-V devices to propagate the data saver status
// to the BPF map. This isn't supported before Android T because BPF maps are
@ -7720,14 +7711,17 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
private void onUserAdded(@NonNull final UserHandle user) {
@Override
public void onUserAdded(@NonNull final UserHandle user) {
if (mOemNetworkPreferences.getNetworkPreferences().size() > 0) {
handleSetOemNetworkPreference(mOemNetworkPreferences, null);
}
updateProfileAllowedNetworks();
mPermissionMonitor.onUserAdded(user);
}
private void onUserRemoved(@NonNull final UserHandle user) {
@Override
public void onUserRemoved(@NonNull final UserHandle user) {
// If there was a network preference for this user, remove it.
handleSetProfileNetworkPreference(
List.of(new ProfileNetworkPreferenceInfo(user, null, true,
@ -7736,9 +7730,32 @@ public class ConnectivityService extends IConnectivityManager.Stub {
if (mOemNetworkPreferences.getNetworkPreferences().size() > 0) {
handleSetOemNetworkPreference(mOemNetworkPreferences, null);
}
mPermissionMonitor.onUserRemoved(user);
}
private void onPackageChanged(@NonNull final String packageName) {
@Override
public void onPackageAdded(@NonNull final String packageName, final int uid) {
handlePackageChanged(packageName);
mPermissionMonitor.onPackageAdded(packageName, uid);
}
@Override
public void onPackageRemoved(@NonNull final String packageName, final int uid) {
handlePackageChanged(packageName);
mPermissionMonitor.onPackageRemoved(packageName, uid);
}
@Override
public void onPackageReplaced(@NonNull final String packageName, final int uid) {
handlePackageChanged(packageName);
}
@Override
public void onExternalApplicationsAvailable(@Nullable String[] pkgList) {
mPermissionMonitor.onExternalApplicationsAvailable(pkgList);
}
private void handlePackageChanged(@NonNull final String packageName) {
// This is necessary in case a package is added or removed, but also when it's replaced to
// run as a new UID by its manifest rules. Also, if a separate package shares the same UID
// as one in the preferences, then it should follow the same routing as that other package,
@ -7754,45 +7771,6 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
private final BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
ensureRunningOnConnectivityServiceThread();
final String action = intent.getAction();
final UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
// User should be filled for below intents, check the existence.
if (user == null) {
Log.wtf(TAG, intent.getAction() + " broadcast without EXTRA_USER");
return;
}
if (Intent.ACTION_USER_ADDED.equals(action)) {
onUserAdded(user);
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
onUserRemoved(user);
} else {
Log.wtf(TAG, "received unexpected intent: " + action);
}
}
};
private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
ensureRunningOnConnectivityServiceThread();
switch (intent.getAction()) {
case Intent.ACTION_PACKAGE_ADDED:
case Intent.ACTION_PACKAGE_REMOVED:
case Intent.ACTION_PACKAGE_REPLACED:
onPackageChanged(intent.getData().getSchemeSpecificPart());
break;
default:
Log.wtf(TAG, "received unexpected intent: " + intent.getAction());
}
}
};
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private final BroadcastReceiver mDataSaverReceiver = new BroadcastReceiver() {
@Override

View file

@ -0,0 +1,230 @@
/*
* 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.connectivity;
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.UserHandle;
import android.util.Log;
import com.android.net.module.util.HandlerUtils;
/**
* Helper class for ConnectivityService to listen for broadcast intents and
* provide callbacks on a given {@link HandlerThread}.
*/
public class BroadcastReceiveHelper {
private static final String TAG = BroadcastReceiveHelper.class.getSimpleName();
private final Context mContext;
private final Handler mHandler;
private final Delegate mCallback;
/**
* Interface defining the callback methods for package and user related events.
*/
public interface Delegate {
/**
* Called when a new package has been installed.
*
* @param packageName The name of the package that was added.
* @param uid The user ID of the application that was added.
*/
void onPackageAdded(String packageName, int uid);
/**
* Called when a package has been uninstalled.
*
* @param packageName The name of the package that was removed.
* @param uid The user ID of the application that was removed.
*/
void onPackageRemoved(String packageName, int uid);
/**
* Called when an existing package has been replaced with a new version.
*
* @param packageName The name of the package that was replaced.
* @param uid The user ID of the application that was replaced.
*/
void onPackageReplaced(String packageName, int uid);
/**
* Called when the availability of external applications changes.
*
* @param pkgList An array of package names that have become available.
*/
void onExternalApplicationsAvailable(String[] pkgList);
/**
* Called when a new user has been added to the device.
*
* @param userHandle The {@link UserHandle} of the user that was added.
*/
void onUserAdded(UserHandle userHandle);
/**
* Called when a user has been removed from the device.
*
* @param userHandle The {@link UserHandle} of the user that was removed.
*/
void onUserRemoved(UserHandle userHandle);
}
/**
* Constructs a new {@code ConnectivityBroadcastReceiveHelper}.
*
* @param context The {@link Context} to register the broadcast receiver.
* @param handler The handler where the callback methods will be executed.
* @param callback The {@link Delegate} implementation that will receive the broadcast events.
*/
public BroadcastReceiveHelper(@NonNull Context context,
@NonNull final Handler handler, @NonNull Delegate callback) {
this.mContext = context;
this.mHandler = handler;
this.mCallback = callback;
}
/**
* Registers the broadcast receivers for package and user related intents.
* The callbacks for these events will be executed on the {@link HandlerThread}
* provided in the constructor.
*/
public void registerReceivers() {
final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
// Listen to user events.
// TODO: Receive intents inline and post them to the handler.
final IntentFilter userIntentFilter = new IntentFilter();
userIntentFilter.addAction(Intent.ACTION_USER_ADDED);
userIntentFilter.addAction(Intent.ACTION_USER_REMOVED);
userAllContext.registerReceiver(mUserIntentReceiver, userIntentFilter,
null /* broadcastPermission */, mHandler);
// Listen to package change events.
final IntentFilter packageIntentFilter = new IntentFilter();
packageIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
packageIntentFilter.addDataScheme("package");
userAllContext.registerReceiver(mPackageIntentReceiver, packageIntentFilter,
null /* broadcastPermission */, mHandler);
// For PermissionMonitor, listen to EXTERNAL_APPLICATIONS_AVAILABLE is that an app
// becoming available means it may need to gain a permission. But an app that becomes
// unavailable can neither gain nor lose permissions on that account, it just can no
// longer run. Thus, doesn't need to listen to EXTERNAL_APPLICATIONS_UNAVAILABLE.
final IntentFilter externalIntentFilter =
new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
userAllContext.registerReceiver(mExternalAppIntentReceiver, externalIntentFilter,
null /* broadcastPermission */, mHandler);
}
/**
* Unregisters the broadcast receiver, stopping the listener from receiving further events.
* It's important to call this method when the listener is no longer needed to avoid
* potential resource leaks.
*/
public void unregisterReceivers() {
try {
mContext.unregisterReceiver(mPackageIntentReceiver);
mContext.unregisterReceiver(mUserIntentReceiver);
mContext.unregisterReceiver(mExternalAppIntentReceiver);
} catch (IllegalArgumentException e) {
// Receiver was not registered
}
}
private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
HandlerUtils.ensureRunningOnHandlerThread(mHandler);
final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
final Uri packageData = intent.getData();
// AOSP never sends null data in the intent (see
// BroadcastHelper#sendPostInstallBroadcasts)
// but OEMs may have modified this code.
if (packageData == null) {
Log.wtf(TAG, "Intent received with null data: " + intent);
return;
}
final String packageName = packageData.getSchemeSpecificPart();
switch (intent.getAction()) {
case Intent.ACTION_PACKAGE_ADDED: {
mCallback.onPackageAdded(packageName, uid);
break;
}
case Intent.ACTION_PACKAGE_REMOVED: {
mCallback.onPackageRemoved(packageName, uid);
break;
}
case Intent.ACTION_PACKAGE_REPLACED: {
mCallback.onPackageReplaced(packageName, uid);
break;
}
default:
Log.wtf(TAG, "received unexpected intent: " + intent.getAction());
}
}
};
private final BroadcastReceiver mExternalAppIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
HandlerUtils.ensureRunningOnHandlerThread(mHandler);
switch (intent.getAction()) {
case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE: {
final String[] pkgList =
intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
mCallback.onExternalApplicationsAvailable(pkgList);
break;
}
default:
Log.wtf(TAG, "received unexpected intent: " + intent.getAction());
}
}
};
private final BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
HandlerUtils.ensureRunningOnHandlerThread(mHandler);
final String action = intent.getAction();
final UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
// User should be filled for below intents, check the existence.
if (user == null) {
Log.wtf(TAG, intent.getAction() + " broadcast without EXTRA_USER");
return;
}
if (Intent.ACTION_USER_ADDED.equals(action)) {
mCallback.onUserAdded(user);
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
mCallback.onUserRemoved(user);
} else {
Log.wtf(TAG, "received unexpected intent: " + action);
}
}
};
}

View file

@ -42,10 +42,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.compat.CompatChanges;
import android.content.AttributionSource;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@ -164,49 +161,6 @@ public class PermissionMonitor {
private static final int MAX_PERMISSION_UPDATE_LOGS = 40;
private final SharedLog mPermissionUpdateLogs = new SharedLog(MAX_PERMISSION_UPDATE_LOGS, TAG);
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
final Uri packageData = intent.getData();
final String packageName =
packageData != null ? packageData.getSchemeSpecificPart() : null;
onPackageAdded(packageName, uid);
} else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
final Uri packageData = intent.getData();
final String packageName =
packageData != null ? packageData.getSchemeSpecificPart() : null;
onPackageRemoved(packageName, uid);
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
final String[] pkgList =
intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
onExternalApplicationsAvailable(pkgList);
} else if (Intent.ACTION_USER_ADDED.equals(action)) {
final UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
// User should be filled for below intents, check the existence.
if (user == null) {
Log.wtf(TAG, action + " broadcast without EXTRA_USER");
return;
}
onUserAdded(user);
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
final UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
// User should be filled for below intents, check the existence.
if (user == null) {
Log.wtf(TAG, action + " broadcast without EXTRA_USER");
return;
}
onUserRemoved(user);
} else {
Log.wtf(TAG, "received unexpected intent: " + action);
}
}
};
/**
* Dependencies of PermissionMonitor, for injection in tests.
*/
@ -462,28 +416,6 @@ public class PermissionMonitor {
final Handler handler = new Handler(mThread.getLooper());
final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
intentFilter.addDataScheme("package");
userAllContext.registerReceiver(
mIntentReceiver, intentFilter, null /* broadcastPermission */, handler);
// Listen to EXTERNAL_APPLICATIONS_AVAILABLE is that an app becoming available means it may
// need to gain a permission. But an app that becomes unavailable can neither gain nor lose
// permissions on that account, it just can no longer run. Thus, doesn't need to listen to
// EXTERNAL_APPLICATIONS_UNAVAILABLE.
final IntentFilter externalIntentFilter =
new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
userAllContext.registerReceiver(
mIntentReceiver, externalIntentFilter, null /* broadcastPermission */, handler);
// Listen for user add/remove.
final IntentFilter userIntentFilter = new IntentFilter();
userIntentFilter.addAction(Intent.ACTION_USER_ADDED);
userIntentFilter.addAction(Intent.ACTION_USER_REMOVED);
userAllContext.registerReceiver(
mIntentReceiver, userIntentFilter, null /* broadcastPermission */, handler);
// Register UIDS_ALLOWED_ON_RESTRICTED_NETWORKS setting observer
mDeps.registerContentObserver(
@ -625,8 +557,8 @@ public class PermissionMonitor {
*
* @hide
*/
@VisibleForTesting
synchronized void onUserAdded(@NonNull UserHandle user) {
public synchronized void onUserAdded(@NonNull UserHandle user) {
ensureRunningOnHandlerThread();
mUsers.add(user);
final List<PackageInfo> apps = getInstalledPackagesAsUser(user);
@ -657,8 +589,8 @@ public class PermissionMonitor {
*
* @hide
*/
@VisibleForTesting
synchronized void onUserRemoved(@NonNull UserHandle user) {
public synchronized void onUserRemoved(@NonNull UserHandle user) {
ensureRunningOnHandlerThread();
mUsers.remove(user);
// Remove uids network permissions that belongs to the user.
@ -843,8 +775,8 @@ public class PermissionMonitor {
*
* @hide
*/
@VisibleForTesting
synchronized void onPackageAdded(@NonNull final String packageName, final int uid) {
public synchronized void onPackageAdded(@NonNull final String packageName, final int uid) {
ensureRunningOnHandlerThread();
// Update uid permission.
updateAppIdTrafficPermission(uid);
// Get the appId permission from all users then send the latest permission to netd.
@ -908,8 +840,8 @@ public class PermissionMonitor {
*
* @hide
*/
@VisibleForTesting
synchronized void onPackageRemoved(@NonNull final String packageName, final int uid) {
public synchronized void onPackageRemoved(@NonNull final String packageName, final int uid) {
ensureRunningOnHandlerThread();
// Update uid permission.
updateAppIdTrafficPermission(uid);
if (BpfNetMaps.isAtLeast25Q2()) {
@ -1307,7 +1239,13 @@ public class PermissionMonitor {
+ ", remove=" + removedUids);
}
private synchronized void onExternalApplicationsAvailable(String[] pkgList) {
/**
* Called when external applications are available.
*
* @param pkgList The package names of the external applications.
*/
public synchronized void onExternalApplicationsAvailable(String[] pkgList) {
ensureRunningOnHandlerThread();
if (CollectionUtils.isEmpty(pkgList)) {
Log.e(TAG, "No available external application.");
return;

View file

@ -0,0 +1,284 @@
/*
* 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.connectivity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
import android.content.Intent.ACTION_PACKAGE_ADDED
import android.content.Intent.ACTION_PACKAGE_REMOVED
import android.content.Intent.ACTION_PACKAGE_REPLACED
import android.content.Intent.ACTION_USER_ADDED
import android.content.Intent.ACTION_USER_REMOVED
import android.content.Intent.EXTRA_CHANGED_PACKAGE_LIST
import android.content.Intent.EXTRA_UID
import android.content.Intent.EXTRA_USER
import android.net.Uri
import android.os.Handler
import android.os.HandlerThread
import android.os.UserHandle
import com.android.compatibility.common.util.MoreMatchers.anyOrNull
import com.android.server.connectivity.BroadcastReceiveHelper.Delegate
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.waitForIdle
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.argThat
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
private const val TEST_PACKAGE_NAME = "com.example.app"
private const val TEST_UID = 1000
private const val TIMEOUT_MS = 2000
private inline fun <reified T> any() = org.mockito.Mockito.any(T::class.java)
@DevSdkIgnoreRunner.MonitorThreadLeak
@RunWith(DevSdkIgnoreRunner::class)
class BroadcastReceiveHelperTest {
private val mockContext = mock(Context::class.java)
private val mockDelegate = mock(Delegate::class.java)
// Create an InOrder object to verify the sequence of delegate calls.
private val delegateInOrderVerifier = org.mockito.Mockito.inOrder(mockDelegate)
// lateinit is used here because thread and handler need to be initialized in the
// @Before setUp method.
private lateinit var handlerThread: HandlerThread
private lateinit var handler: Handler
private lateinit var broadcastReceiveHelper: BroadcastReceiveHelper
private lateinit var packageReceiver: BroadcastReceiver
private lateinit var externalAppReceiver: BroadcastReceiver
private lateinit var userReceiver: BroadcastReceiver
@Before
fun setUp() {
handlerThread = HandlerThread("TestThread")
handlerThread.start()
handler = Handler(handlerThread.looper)
broadcastReceiveHelper = BroadcastReceiveHelper(mockContext, handler, mockDelegate)
// Capture intent receivers.
doReturn(mockContext).`when`(mockContext).createContextAsUser(UserHandle.ALL, 0)
broadcastReceiveHelper.registerReceivers()
userReceiver = captureIntentReceiver(ACTION_USER_ADDED)
packageReceiver = captureIntentReceiver(ACTION_PACKAGE_ADDED)
externalAppReceiver = captureIntentReceiver(ACTION_EXTERNAL_APPLICATIONS_AVAILABLE)
}
private fun captureIntentReceiver(action: String): BroadcastReceiver {
val receiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver::class.java)
verify(mockContext, times(1)).registerReceiver(
receiverCaptor.capture(),
argThat { it.hasAction(action) },
anyOrNull(String::class.java),
eq(handler)
)
// Return the first match.
return receiverCaptor.value
}
@After
fun tearDown() {
broadcastReceiveHelper.unregisterReceivers()
handlerThread.quitSafely()
handlerThread.join()
}
@Test
fun testPackageAdded() {
val intent = Intent(ACTION_PACKAGE_ADDED).apply {
data = Uri.fromParts("package", TEST_PACKAGE_NAME, null)
putExtra(EXTRA_UID, TEST_UID)
}
processOnHandlerThread { packageReceiver.onReceive(mockContext, intent) }
delegateInOrderVerifier.verify(mockDelegate).onPackageAdded(TEST_PACKAGE_NAME, TEST_UID)
delegateInOrderVerifier.verifyNoMoreInteractions()
}
@Test
fun testPackageRemoved() {
val intent = Intent(ACTION_PACKAGE_REMOVED).apply {
data = Uri.fromParts("package", TEST_PACKAGE_NAME, null)
putExtra(EXTRA_UID, TEST_UID)
}
processOnHandlerThread { packageReceiver.onReceive(mockContext, intent) }
delegateInOrderVerifier.verify(mockDelegate).onPackageRemoved(TEST_PACKAGE_NAME, TEST_UID)
delegateInOrderVerifier.verifyNoMoreInteractions()
}
@Test
fun testPackageReplaced() {
val intent = Intent(ACTION_PACKAGE_REPLACED).apply {
data = Uri.fromParts("package", TEST_PACKAGE_NAME, null)
putExtra(EXTRA_UID, TEST_UID)
}
processOnHandlerThread { packageReceiver.onReceive(mockContext, intent) }
delegateInOrderVerifier.verify(mockDelegate).onPackageReplaced(TEST_PACKAGE_NAME, TEST_UID)
delegateInOrderVerifier.verifyNoMoreInteractions()
}
@Test
fun testExternalAppsAvailable() {
val packageList = arrayOf("com.example.app1", "com.example.app2")
val intent = Intent(ACTION_EXTERNAL_APPLICATIONS_AVAILABLE).apply {
putExtra(EXTRA_CHANGED_PACKAGE_LIST, packageList)
}
processOnHandlerThread { externalAppReceiver.onReceive(mockContext, intent) }
delegateInOrderVerifier.verify(mockDelegate).onExternalApplicationsAvailable(packageList)
delegateInOrderVerifier.verifyNoMoreInteractions()
}
@Test
fun testUserAdded() {
val userHandle = mock(UserHandle::class.java)
val intent = Intent(ACTION_USER_ADDED).apply {
putExtra(EXTRA_USER, userHandle)
}
processOnHandlerThread { userReceiver.onReceive(mockContext, intent) }
delegateInOrderVerifier.verify(mockDelegate).onUserAdded(userHandle)
delegateInOrderVerifier.verifyNoMoreInteractions()
}
@Test
fun testUserRemoved() {
val userHandle = mock(UserHandle::class.java)
val intent = Intent(ACTION_USER_REMOVED).apply {
putExtra(EXTRA_USER, userHandle)
}
processOnHandlerThread { userReceiver.onReceive(mockContext, intent) }
delegateInOrderVerifier.verify(mockDelegate).onUserRemoved(userHandle)
delegateInOrderVerifier.verifyNoMoreInteractions()
}
@Test
fun testUnregisterReceivers() {
broadcastReceiveHelper.unregisterReceivers()
verify(mockContext).unregisterReceiver(userReceiver)
verify(mockContext).unregisterReceiver(packageReceiver)
verify(mockContext).unregisterReceiver(externalAppReceiver)
}
@Test
fun testBroadcastOrder() {
val numEvents = 50
val random = java.util.Random()
val eventList = mutableListOf<Intent>()
// Function to append a test package name extra.
fun Intent.withPackageNameForIndex(index: Int): Intent {
data = Uri.fromParts("package", "com.example.app$index", null)
putExtra(EXTRA_UID, index)
return this
}
// Function to append a mock UserHandle extra.
fun Intent.withUserHandleForIndex(index: Int): Intent {
putExtra(EXTRA_USER, UserHandle.of(index))
return this
}
// Generate a list of 50 random actions and their corresponding intents.
repeat(numEvents) { index ->
val intent = when (random.nextInt(6)) {
0 -> Intent(ACTION_PACKAGE_ADDED).withPackageNameForIndex(index)
1 -> Intent(ACTION_PACKAGE_REMOVED).withPackageNameForIndex(index)
2 -> Intent(ACTION_PACKAGE_REPLACED).withPackageNameForIndex(index)
3 -> Intent(ACTION_EXTERNAL_APPLICATIONS_AVAILABLE).apply {
val packageList = arrayOf("com.example.app$index")
putExtra(EXTRA_CHANGED_PACKAGE_LIST, packageList)
}
4 -> Intent(ACTION_USER_ADDED).withUserHandleForIndex(index)
5 -> Intent(ACTION_USER_REMOVED).withUserHandleForIndex(index)
else -> throw IllegalStateException("Unexpected random number")
}
eventList.add(intent)
}
// Post all intents to the handler thread at once.
eventList.forEach {
handler.post {
when (it.action) {
ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED, ACTION_PACKAGE_REPLACED ->
packageReceiver.onReceive(mockContext, it)
ACTION_EXTERNAL_APPLICATIONS_AVAILABLE ->
externalAppReceiver.onReceive(mockContext, it)
ACTION_USER_ADDED, ACTION_USER_REMOVED ->
userReceiver.onReceive(mockContext, it)
}
}
}
// Wait for all intents to be processed at once.
handler.waitForIdle(TIMEOUT_MS)
// Verify the order of delegate calls.
eventList.forEach {
when (it.action) {
ACTION_PACKAGE_ADDED -> delegateInOrderVerifier.verify(mockDelegate)
.onPackageAdded(
it.data?.schemeSpecificPart!!,
it.getIntExtra(EXTRA_UID, -1)
)
ACTION_PACKAGE_REMOVED -> delegateInOrderVerifier.verify(mockDelegate)
.onPackageRemoved(
it.data?.schemeSpecificPart!!,
it.getIntExtra(EXTRA_UID, -1)
)
ACTION_PACKAGE_REPLACED -> delegateInOrderVerifier.verify(mockDelegate)
.onPackageReplaced(
it.data?.schemeSpecificPart!!,
it.getIntExtra(EXTRA_UID, -1)
)
ACTION_EXTERNAL_APPLICATIONS_AVAILABLE ->
delegateInOrderVerifier.verify(mockDelegate)
.onExternalApplicationsAvailable(it.getStringArrayExtra(
EXTRA_CHANGED_PACKAGE_LIST
)!!)
ACTION_USER_ADDED -> delegateInOrderVerifier.verify(mockDelegate)
.onUserAdded(it.getParcelableExtra(EXTRA_USER)!!)
ACTION_USER_REMOVED -> delegateInOrderVerifier.verify(mockDelegate)
.onUserRemoved(it.getParcelableExtra(EXTRA_USER)!!)
}
}
// Ensure no other calls were made to the delegate.
delegateInOrderVerifier.verifyNoMoreInteractions()
}
private fun processOnHandlerThread(function: Runnable) {
handler.post { function.run() }
handler.waitForIdle(HANDLER_TIMEOUT_MS)
}
}

View file

@ -47,7 +47,6 @@ import static android.permission.PermissionManager.PERMISSION_GRANTED;
import static com.android.server.connectivity.PermissionMonitor.isHigherNetworkPermission;
import static com.android.testutils.TestPermissionUtil.runAsShell;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import static junit.framework.Assert.fail;
@ -76,9 +75,7 @@ import static org.mockito.Mockito.when;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.AttributionSource;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@ -109,6 +106,7 @@ import com.android.networkstack.apishim.ProcessShimImpl;
import com.android.networkstack.apishim.common.ProcessShim;
import com.android.server.BpfNetMaps;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.HandlerUtils;
@ -361,6 +359,10 @@ public class PermissionMonitorTest {
processOnHandlerThread(() -> mPermissionMonitor.onPackageRemoved(packageName, uid));
}
private void onExternalApplicationsAvailable(String [] pkgList) {
processOnHandlerThread(() -> mPermissionMonitor.onExternalApplicationsAvailable(pkgList));
}
private void sendAppIdsTrafficPermission(SparseIntArray netdPermissionsAppIds) {
processOnHandlerThread(() ->
mPermissionMonitor.sendAppIdsTrafficPermission(netdPermissionsAppIds));
@ -1433,27 +1435,6 @@ public class PermissionMonitorTest {
mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID2);
}
private BroadcastReceiver expectBroadcastReceiver(String... actions) {
final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
verify(mContext, times(1)).registerReceiver(receiverCaptor.capture(),
argThat(filter -> {
for (String action : actions) {
if (!filter.hasAction(action)) {
return false;
}
}
return true;
}), any(), any());
final BroadcastReceiver originalReceiver = receiverCaptor.getValue();
return new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
processOnHandlerThread(() -> originalReceiver.onReceive(context, intent));
}
};
}
private void processOnHandlerThread(Runnable function) {
final Handler handler = mHandlerThread.getThreadHandler();
handler.post(() -> function.run());
@ -1462,26 +1443,13 @@ public class PermissionMonitorTest {
@Test
@EnableCompatChanges(RESTRICT_LOCAL_NETWORK)
public void testIntentReceiver() throws Exception {
public void testUidPermissionWhenPackageAddedRemoved() throws Exception {
startMonitoring();
final BroadcastReceiver receiver = expectBroadcastReceiver(
Intent.ACTION_PACKAGE_ADDED, Intent.ACTION_PACKAGE_REMOVED);
// Verify receiving PACKAGE_ADDED intent.
final Intent addedIntent = new Intent(Intent.ACTION_PACKAGE_ADDED,
Uri.fromParts("package", MOCK_PACKAGE1, null /* fragment */));
addedIntent.putExtra(Intent.EXTRA_UID, MOCK_UID11);
buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE1, MOCK_UID11, INTERNET,
UPDATE_DEVICE_STATS);
receiver.onReceive(mContext, addedIntent);
// Add/Remove package and verify uid permissions.
addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET, UPDATE_DEVICE_STATS);
mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
// Verify receiving PACKAGE_REMOVED intent.
when(mPackageManager.getPackagesForUid(MOCK_UID11)).thenReturn(new String[]{});
final Intent removedIntent = new Intent(Intent.ACTION_PACKAGE_REMOVED,
Uri.fromParts("package", MOCK_PACKAGE1, null /* fragment */));
removedIntent.putExtra(Intent.EXTRA_UID, MOCK_UID11);
receiver.onReceive(mContext, removedIntent);
removePackage(MOCK_PACKAGE1, MOCK_UID11);
mBpfMapMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, MOCK_APPID1);
}
@ -1634,17 +1602,12 @@ public class PermissionMonitorTest {
mNetdMonitor.expectNoNetworkPerm(new UserHandle[]{MOCK_USER1}, MOCK_APPID1, MOCK_APPID2);
mBpfMapMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID1, MOCK_APPID2);
final BroadcastReceiver receiver = expectBroadcastReceiver(
Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
// Verify receiving EXTERNAL_APPLICATIONS_AVAILABLE intent and update permission to netd.
final Intent externalIntent = new Intent(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
externalIntent.putExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST,
new String[] { MOCK_PACKAGE1 , MOCK_PACKAGE2});
// Call onExternalApplicationsAvailable and verify update permission to netd.
buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE1, MOCK_UID11,
CONNECTIVITY_USE_RESTRICTED_NETWORKS, INTERNET);
buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE2, MOCK_UID12, CHANGE_NETWORK_STATE,
UPDATE_DEVICE_STATS);
receiver.onReceive(mContext, externalIntent);
onExternalApplicationsAvailable(new String[] { MOCK_PACKAGE1 , MOCK_PACKAGE2});
mNetdMonitor.expectNetworkPerm(PERMISSION_SYSTEM, new UserHandle[]{MOCK_USER1},
MOCK_APPID1);
mNetdMonitor.expectNetworkPerm(PERMISSION_NETWORK, new UserHandle[]{MOCK_USER1},
@ -1658,8 +1621,6 @@ public class PermissionMonitorTest {
public void testOnExternalApplicationsAvailable_AppsNotRegisteredOnStartMonitoring()
throws Exception {
startMonitoring();
final BroadcastReceiver receiver = expectBroadcastReceiver(
Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
// Initial the permission state. MOCK_PACKAGE1 and MOCK_PACKAGE2 are installed on external
// and have different uids. There has no permission for both uids.
@ -1668,11 +1629,8 @@ public class PermissionMonitorTest {
buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE2, MOCK_UID12, CHANGE_NETWORK_STATE,
UPDATE_DEVICE_STATS);
// Verify receiving EXTERNAL_APPLICATIONS_AVAILABLE intent and update permission to netd.
final Intent externalIntent = new Intent(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
externalIntent.putExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST,
new String[] { MOCK_PACKAGE1 , MOCK_PACKAGE2});
receiver.onReceive(mContext, externalIntent);
// Call onExternalApplicationsAvailable and verify update permission to netd.
onExternalApplicationsAvailable(new String[] { MOCK_PACKAGE1 , MOCK_PACKAGE2});
mNetdMonitor.expectNetworkPerm(PERMISSION_SYSTEM, new UserHandle[]{MOCK_USER1},
MOCK_APPID1);
mNetdMonitor.expectNetworkPerm(PERMISSION_NETWORK, new UserHandle[]{MOCK_USER1},
@ -1695,14 +1653,10 @@ public class PermissionMonitorTest {
mNetdMonitor.expectNoNetworkPerm(new UserHandle[]{MOCK_USER1}, MOCK_APPID1);
mBpfMapMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID1);
final BroadcastReceiver receiver = expectBroadcastReceiver(
Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
// Verify receiving EXTERNAL_APPLICATIONS_AVAILABLE intent and update permission to netd.
final Intent externalIntent = new Intent(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
externalIntent.putExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST, new String[] {MOCK_PACKAGE1});
// Call onExternalApplicationsAvailable and verify update permission to netd.
buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE1, MOCK_UID11, CHANGE_NETWORK_STATE);
buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE2, MOCK_UID11, UPDATE_DEVICE_STATS);
receiver.onReceive(mContext, externalIntent);
onExternalApplicationsAvailable(new String[] {MOCK_PACKAGE1});
mNetdMonitor.expectNetworkPerm(PERMISSION_NETWORK, new UserHandle[]{MOCK_USER1},
MOCK_APPID1);
mBpfMapMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, MOCK_APPID1);
@ -1724,16 +1678,12 @@ public class PermissionMonitorTest {
MOCK_APPID1);
mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
final BroadcastReceiver receiver = expectBroadcastReceiver(
Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
// Verify receiving EXTERNAL_APPLICATIONS_AVAILABLE intent and update permission to netd.
final Intent externalIntent = new Intent(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
externalIntent.putExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST, new String[] {MOCK_PACKAGE1});
// Call onExternalApplicationsAvailable and verify update permission to netd.
buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE1, MOCK_UID11,
CONNECTIVITY_USE_RESTRICTED_NETWORKS, UPDATE_DEVICE_STATS);
buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE2, MOCK_UID11, CHANGE_NETWORK_STATE,
INTERNET);
receiver.onReceive(mContext, externalIntent);
onExternalApplicationsAvailable(new String[] {MOCK_PACKAGE1});
mNetdMonitor.expectNetworkPerm(PERMISSION_SYSTEM, new UserHandle[]{MOCK_USER1},
MOCK_APPID1);
mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);