diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index fa7dfa9846..8137551353 100644 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -249,6 +249,7 @@ import com.android.net.module.util.NetworkCapabilitiesUtils; import com.android.net.module.util.PermissionUtils; import com.android.net.module.util.netlink.InetDiagMessage; import com.android.server.connectivity.AutodestructReference; +import com.android.server.connectivity.CarrierPrivilegeAuthenticator; import com.android.server.connectivity.ConnectivityFlags; import com.android.server.connectivity.DnsManager; import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate; @@ -593,6 +594,13 @@ public class ConnectivityService extends IConnectivityManager.Stub // Handle private DNS validation status updates. private static final int EVENT_PRIVATE_DNS_VALIDATION_UPDATE = 38; + /** + * used to remove a network request, either a listener or a real request and call unavailable + * arg1 = UID of caller + * obj = NetworkRequest + */ + private static final int EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE = 39; + /** * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has * been tested. @@ -760,6 +768,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private Set mWolSupportedInterfaces; private final TelephonyManager mTelephonyManager; + private final CarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator; private final AppOpsManager mAppOpsManager; private final LocationPermissionChecker mLocationPermissionChecker; @@ -1406,6 +1415,12 @@ public class ConnectivityService extends IConnectivityManager.Stub mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext); + if (SdkLevel.isAtLeastT()) { + mCarrierPrivilegeAuthenticator = + new CarrierPrivilegeAuthenticator(mContext, mTelephonyManager); + } else { + mCarrierPrivilegeAuthenticator = null; + } // To ensure uid state is synchronized with Network Policy, register for // NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService @@ -4140,6 +4155,15 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + private boolean hasCarrierPrivilegeForNetworkRequest(int callingUid, + NetworkRequest networkRequest) { + if (SdkLevel.isAtLeastT() && mCarrierPrivilegeAuthenticator != null) { + return mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(callingUid, + networkRequest); + } + return false; + } + private void handleRegisterNetworkRequestWithIntent(@NonNull final Message msg) { final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj); // handleRegisterNetworkRequestWithIntent() doesn't apply to multilayer requests. @@ -4163,6 +4187,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleRegisterNetworkRequests(@NonNull final Set nris) { ensureRunningOnConnectivityServiceThread(); + NetworkRequest requestToBeReleased = null; for (final NetworkRequestInfo nri : nris) { mNetworkRequestInfoLogs.log("REGISTER " + nri); checkNrisConsistency(nri); @@ -4177,7 +4202,15 @@ public class ConnectivityService extends IConnectivityManager.Stub } } } + if (req.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) { + if (!hasCarrierPrivilegeForNetworkRequest(nri.mUid, req) + && !checkConnectivityRestrictedNetworksPermission( + nri.mPid, nri.mUid)) { + requestToBeReleased = req; + } + } } + // If this NRI has a satisfier already, it is replacing an older request that // has been removed. Track it. final NetworkRequest activeRequest = nri.getActiveRequest(); @@ -4187,6 +4220,11 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + if (requestToBeReleased != null) { + releaseNetworkRequestAndCallOnUnavailable(requestToBeReleased); + return; + } + if (mFlags.noRematchAllRequestsOnRegister()) { rematchNetworksAndRequests(nris); } else { @@ -5026,6 +5064,11 @@ public class ConnectivityService extends IConnectivityManager.Stub /* callOnUnavailable */ false); break; } + case EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE: { + handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1, + /* callOnUnavailable */ true); + break; + } case EVENT_SET_ACCEPT_UNVALIDATED: { Network network = (Network) msg.obj; handleSetAcceptUnvalidated(network, toBool(msg.arg1), toBool(msg.arg2)); @@ -6330,12 +6373,24 @@ public class ConnectivityService extends IConnectivityManager.Stub private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities, String callingPackageName, String callingAttributionTag) { if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) { - enforceConnectivityRestrictedNetworksPermission(); + if (!networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) { + enforceConnectivityRestrictedNetworksPermission(); + } } else { enforceChangePermission(callingPackageName, callingAttributionTag); } } + private boolean checkConnectivityRestrictedNetworksPermission(int callerPid, int callerUid) { + if (checkAnyPermissionOf(callerPid, callerUid, + android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS) + || checkAnyPermissionOf(callerPid, callerUid, + android.Manifest.permission.CONNECTIVITY_INTERNAL)) { + return true; + } + return false; + } + @Override public boolean requestBandwidthUpdate(Network network) { enforceAccessPermission(); @@ -6402,7 +6457,6 @@ public class ConnectivityService extends IConnectivityManager.Stub ensureValidNetworkSpecifier(networkCapabilities); restrictRequestUidsForCallerAndSetRequestorInfo(networkCapabilities, callingUid, callingPackageName); - NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.REQUEST); NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, networkRequest, operation, @@ -6518,6 +6572,13 @@ public class ConnectivityService extends IConnectivityManager.Stub EVENT_RELEASE_NETWORK_REQUEST, mDeps.getCallingUid(), 0, networkRequest)); } + private void releaseNetworkRequestAndCallOnUnavailable(NetworkRequest networkRequest) { + ensureNetworkRequestHasType(networkRequest); + mHandler.sendMessage(mHandler.obtainMessage( + EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE, mDeps.getCallingUid(), 0, + networkRequest)); + } + private void handleRegisterNetworkProvider(NetworkProviderInfo npi) { if (mNetworkProviderInfos.containsKey(npi.messenger)) { // Avoid creating duplicates. even if an app makes a direct AIDL call. diff --git a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java new file mode 100644 index 0000000000..c5107ade9c --- /dev/null +++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2022 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 static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; + +import android.annotation.NonNull; +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.PackageManager; +import android.net.NetworkRequest; +import android.net.NetworkSpecifier; +import android.net.TelephonyNetworkSpecifier; +import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Process; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.networkstack.apishim.TelephonyManagerShimImpl; +import com.android.networkstack.apishim.common.TelephonyManagerShim; +import com.android.networkstack.apishim.common.TelephonyManagerShim.CarrierPrivilegesListenerShim; +import com.android.networkstack.apishim.common.UnsupportedApiLevelException; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; + +/** + * Tracks the uid of the carrier privileged app that provides the carrier config. + * Authenticates if the caller has same uid as + * carrier privileged app that provides the carrier config + * @hide + */ +public class CarrierPrivilegeAuthenticator extends BroadcastReceiver { + private static final String TAG = CarrierPrivilegeAuthenticator.class.getSimpleName(); + private static final boolean DBG = true; + + // The context is for the current user (system server) + private final Context mContext; + private final TelephonyManagerShim mTelephonyManagerShim; + private final TelephonyManager mTelephonyManager; + @GuardedBy("mLock") + private int[] mCarrierServiceUid; + @GuardedBy("mLock") + private int mModemCount = 0; + private final Object mLock = new Object(); + private final HandlerThread mThread; + private final Handler mHandler; + @NonNull + private final List mCarrierPrivilegesChangedListeners = + new ArrayList<>(); + + public CarrierPrivilegeAuthenticator(@NonNull final Context c, + @NonNull final TelephonyManager t, + @NonNull final TelephonyManagerShimImpl telephonyManagerShim) { + mContext = c; + mTelephonyManager = t; + mTelephonyManagerShim = telephonyManagerShim; + mThread = new HandlerThread(TAG); + mThread.start(); + mHandler = new Handler(mThread.getLooper()) {}; + synchronized (mLock) { + mModemCount = mTelephonyManager.getActiveModemCount(); + registerForCarrierChanges(); + updateCarrierServiceUid(); + } + } + + public CarrierPrivilegeAuthenticator(@NonNull final Context c, + @NonNull final TelephonyManager t) { + mContext = c; + mTelephonyManager = t; + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S) { + mTelephonyManagerShim = new TelephonyManagerShimImpl(mTelephonyManager); + } else { + mTelephonyManagerShim = null; + } + mThread = new HandlerThread(TAG); + mThread.start(); + mHandler = new Handler(mThread.getLooper()) {}; + synchronized (mLock) { + mModemCount = mTelephonyManager.getActiveModemCount(); + registerForCarrierChanges(); + updateCarrierServiceUid(); + } + } + + /** + * An adapter {@link Executor} that posts all executed tasks onto the given + * {@link Handler}. + * + * TODO : migrate to the version in frameworks/libs/net when it's ready + * + * @hide + */ + public class HandlerExecutor implements Executor { + private final Handler mHandler; + public HandlerExecutor(@NonNull Handler handler) { + mHandler = handler; + } + @Override + public void execute(Runnable command) { + if (!mHandler.post(command)) { + throw new RejectedExecutionException(mHandler + " is shutting down"); + } + } + } + + /** + * Broadcast receiver for ACTION_MULTI_SIM_CONFIG_CHANGED + * + *

The broadcast receiver is registered with mHandler + */ + @Override + public void onReceive(Context context, Intent intent) { + switch (intent.getAction()) { + case TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED: + handleActionMultiSimConfigChanged(context, intent); + break; + default: + Log.d(TAG, "Unknown intent received with action: " + intent.getAction()); + } + } + + private void handleActionMultiSimConfigChanged(Context context, Intent intent) { + unregisterCarrierPrivilegesListeners(); + synchronized (mLock) { + mModemCount = mTelephonyManager.getActiveModemCount(); + } + registerCarrierPrivilegesListeners(); + updateCarrierServiceUid(); + } + + private void registerForCarrierChanges() { + final IntentFilter filter = new IntentFilter(); + filter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED); + mContext.registerReceiver(this, filter, null, mHandler); + registerCarrierPrivilegesListeners(); + } + + private void registerCarrierPrivilegesListeners() { + final HandlerExecutor executor = new HandlerExecutor(mHandler); + int modemCount; + synchronized (mLock) { + modemCount = mModemCount; + } + try { + for (int i = 0; i < modemCount; i++) { + CarrierPrivilegesListenerShim carrierPrivilegesListener = + new CarrierPrivilegesListenerShim() { + @Override + public void onCarrierPrivilegesChanged( + @NonNull List privilegedPackageNames, + @NonNull int[] privilegedUids) { + // Re-trigger the synchronous check (which is also very cheap due + // to caching in CarrierPrivilegesTracker). This allows consistency + // with the onSubscriptionsChangedListener and broadcasts. + updateCarrierServiceUid(); + } + }; + addCarrierPrivilegesListener(i, executor, carrierPrivilegesListener); + mCarrierPrivilegesChangedListeners.add(carrierPrivilegesListener); + } + } catch (IllegalArgumentException e) { + Log.e(TAG, "Encountered exception registering carrier privileges listeners", e); + } + } + + private void addCarrierPrivilegesListener(int logicalSlotIndex, Executor executor, + CarrierPrivilegesListenerShim listener) { + if (mTelephonyManagerShim == null) { + return; + } + try { + mTelephonyManagerShim.addCarrierPrivilegesListener( + logicalSlotIndex, executor, listener); + } catch (UnsupportedApiLevelException unsupportedApiLevelException) { + Log.e(TAG, "addCarrierPrivilegesListener API is not available"); + } + } + + private void removeCarrierPrivilegesListener(CarrierPrivilegesListenerShim listener) { + if (mTelephonyManagerShim == null) { + return; + } + try { + mTelephonyManagerShim.removeCarrierPrivilegesListener(listener); + } catch (UnsupportedApiLevelException unsupportedApiLevelException) { + Log.e(TAG, "removeCarrierPrivilegesListener API is not available"); + } + } + + private String getCarrierServicePackageNameForLogicalSlot(int logicalSlotIndex) { + if (mTelephonyManagerShim == null) { + return null; + } + try { + return mTelephonyManagerShim.getCarrierServicePackageNameForLogicalSlot( + logicalSlotIndex); + } catch (UnsupportedApiLevelException unsupportedApiLevelException) { + Log.e(TAG, "getCarrierServicePackageNameForLogicalSlot API is not available"); + } + return null; + } + + private void unregisterCarrierPrivilegesListeners() { + for (CarrierPrivilegesListenerShim carrierPrivilegesListener : + mCarrierPrivilegesChangedListeners) { + removeCarrierPrivilegesListener(carrierPrivilegesListener); + } + mCarrierPrivilegesChangedListeners.clear(); + } + + /** + * Check if network request is allowed based upon carrrier service package. + * + * Network request for {@link NET_CAPABILITY_CBS} is allowed if the caller has + * carrier privilege and provides the carrier config. This function checks if caller + * has the same and returns true if it has else false. + * + * @param callingUid user identifier that uniquely identifies the caller. + * @param networkRequest the network request for which the carrier privilege is checked. + * @return true if caller has carrier privilege and provides the carrier config else false. + */ + public boolean hasCarrierPrivilegeForNetworkRequest(int callingUid, + NetworkRequest networkRequest) { + if (callingUid != Process.INVALID_UID) { + final int subId = getSubIdFromNetworkSpecifier( + networkRequest.getNetworkSpecifier()); + return callingUid == getCarrierServiceUidForSubId(subId); + } + return false; + } + + @VisibleForTesting + void updateCarrierServiceUid() { + synchronized (mLock) { + mCarrierServiceUid = new int[mModemCount]; + for (int i = 0; i < mModemCount; i++) { + mCarrierServiceUid[i] = getCarrierServicePackageUidForSlot(i); + } + } + } + + @VisibleForTesting + int getCarrierServiceUidForSubId(int subId) { + final int slotId = getSlotIndex(subId); + synchronized (mLock) { + if (slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX && slotId < mModemCount) { + return mCarrierServiceUid[slotId]; + } + } + return Process.INVALID_UID; + } + + @VisibleForTesting + protected int getSlotIndex(int subId) { + return SubscriptionManager.getSlotIndex(subId); + } + + @VisibleForTesting + int getSubIdFromNetworkSpecifier(NetworkSpecifier specifier) { + if (specifier instanceof TelephonyNetworkSpecifier) { + return ((TelephonyNetworkSpecifier) specifier).getSubscriptionId(); + } + return SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; + } + + @VisibleForTesting + int getUidForPackage(String pkgName) { + if (pkgName == null) { + return Process.INVALID_UID; + } + try { + PackageManager pm = mContext.getPackageManager(); + if (pm != null) { + ApplicationInfo applicationInfo = pm.getApplicationInfo(pkgName, 0); + if (applicationInfo != null) { + return applicationInfo.uid; + } + } + } catch (PackageManager.NameNotFoundException exception) { + // Didn't find package. Try other users + Log.i(TAG, "Unable to find uid for package " + pkgName); + } + return Process.INVALID_UID; + } + + @VisibleForTesting + int getCarrierServicePackageUidForSlot(int slotId) { + return getUidForPackage(getCarrierServicePackageNameForLogicalSlot(slotId)); + } +} diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index 652aee9084..8f67e485a7 100644 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -5727,6 +5727,22 @@ public class ConnectivityServiceTest { } } + /** + * Validate the callback flow CBS request without carrier privilege. + */ + @Test + public void testCBSRequestWithoutCarrierPrivilege() throws Exception { + final NetworkRequest nr = new NetworkRequest.Builder().addTransportType( + TRANSPORT_CELLULAR).addCapability(NET_CAPABILITY_CBS).build(); + final TestNetworkCallback networkCallback = new TestNetworkCallback(); + + mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_DENIED); + // Now file the test request and expect it. + mCm.requestNetwork(nr, networkCallback); + networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null); + mCm.unregisterNetworkCallback(networkCallback); + } + private static class TestKeepaliveCallback extends PacketKeepaliveCallback { public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR } diff --git a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java new file mode 100644 index 0000000000..d193aa9819 --- /dev/null +++ b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2022 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 static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED; + +import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.net.NetworkRequest; +import android.net.TelephonyNetworkSpecifier; +import android.telephony.TelephonyManager; + +import com.android.networkstack.apishim.TelephonyManagerShimImpl; +import com.android.networkstack.apishim.common.TelephonyManagerShim; +import com.android.networkstack.apishim.common.UnsupportedApiLevelException; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; +import com.android.testutils.DevSdkIgnoreRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +import java.util.Collections; +import java.util.List; + +/** + * Tests for CarrierPrivilegeAuthenticatorTest. + * + * Build, install and run with: + * runtest frameworks-net -c com.android.server.connectivity.CarrierPrivilegeAuthenticatorTest + */ +@RunWith(DevSdkIgnoreRunner.class) +@IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available +public class CarrierPrivilegeAuthenticatorTest { + private static final String PACKAGE_NAME = + CarrierPrivilegeAuthenticatorTest.class.getPackage().getName(); + private static final int TEST_SIM_SLOT_INDEX = 0; + private static final int TEST_SUBSCRIPTION_ID_1 = 2; + private static final int TEST_SUBSCRIPTION_ID_2 = 3; + + @NonNull private final Context mContext; + @NonNull private final TelephonyManager mTelephonyManager; + @NonNull private final TelephonyManagerShimImpl mTelephonyManagerShim; + @NonNull private final PackageManager mPackageManager; + @NonNull private CarrierPrivilegeAuthenticatorChild mCarrierPrivilegeAuthenticator; + private final int mCarrierConfigPkgUid = 12345; + private final String mTestPkg = "com.android.server.connectivity.test"; + + public class CarrierPrivilegeAuthenticatorChild extends CarrierPrivilegeAuthenticator { + CarrierPrivilegeAuthenticatorChild(@NonNull final Context c, + @NonNull final TelephonyManager t) { + super(c, t, mTelephonyManagerShim); + } + @Override + protected int getSlotIndex(int subId) { + return subId; + } + } + + public CarrierPrivilegeAuthenticatorTest() { + mContext = mock(Context.class); + mTelephonyManager = mock(TelephonyManager.class); + mTelephonyManagerShim = mock(TelephonyManagerShimImpl.class); + mPackageManager = mock(PackageManager.class); + } + + @Before + public void setUp() throws Exception { + doReturn(2).when(mTelephonyManager).getActiveModemCount(); + doReturn(mTestPkg).when(mTelephonyManagerShim) + .getCarrierServicePackageNameForLogicalSlot(anyInt()); + doReturn(mPackageManager).when(mContext).getPackageManager(); + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.uid = mCarrierConfigPkgUid; + doReturn(applicationInfo).when(mPackageManager) + .getApplicationInfo(eq(mTestPkg), anyInt()); + mCarrierPrivilegeAuthenticator = + new CarrierPrivilegeAuthenticatorChild(mContext, mTelephonyManager); + } + + private IntentFilter getIntentFilter() { + final ArgumentCaptor captor = ArgumentCaptor.forClass(IntentFilter.class); + verify(mContext).registerReceiver(any(), captor.capture(), any(), any()); + return captor.getValue(); + } + + private List + getCarrierPrivilegesListeners() { + final ArgumentCaptor captor = + ArgumentCaptor.forClass(TelephonyManagerShim.CarrierPrivilegesListenerShim.class); + try { + verify(mTelephonyManagerShim, atLeastOnce()) + .addCarrierPrivilegesListener(anyInt(), any(), captor.capture()); + } catch (UnsupportedApiLevelException e) { + + } + return captor.getAllValues(); + } + + private Intent buildTestMultiSimConfigBroadcastIntent() { + final Intent intent = new Intent(ACTION_MULTI_SIM_CONFIG_CHANGED); + return intent; + } + @Test + public void testConstructor() throws Exception { + verify(mContext).registerReceiver( + eq(mCarrierPrivilegeAuthenticator), + any(IntentFilter.class), + any(), + any()); + final IntentFilter filter = getIntentFilter(); + assertEquals(1, filter.countActions()); + assertTrue(filter.hasAction(ACTION_MULTI_SIM_CONFIG_CHANGED)); + + verify(mTelephonyManagerShim, times(2)) + .addCarrierPrivilegesListener(anyInt(), any(), any()); + verify(mTelephonyManagerShim) + .addCarrierPrivilegesListener(eq(0), any(), any()); + verify(mTelephonyManagerShim) + .addCarrierPrivilegesListener(eq(1), any(), any()); + assertEquals(2, getCarrierPrivilegesListeners().size()); + + final TelephonyNetworkSpecifier telephonyNetworkSpecifier = + new TelephonyNetworkSpecifier(0); + final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder(); + networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR); + networkRequestBuilder.setNetworkSpecifier(telephonyNetworkSpecifier); + + assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest( + mCarrierConfigPkgUid, networkRequestBuilder.build())); + assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest( + mCarrierConfigPkgUid + 1, networkRequestBuilder.build())); + } + + @Test + public void testMultiSimConfigChanged() throws Exception { + doReturn(1).when(mTelephonyManager).getActiveModemCount(); + final List carrierPrivilegesListeners = + getCarrierPrivilegesListeners(); + + mCarrierPrivilegeAuthenticator.onReceive( + mContext, buildTestMultiSimConfigBroadcastIntent()); + for (TelephonyManagerShim.CarrierPrivilegesListenerShim carrierPrivilegesListener + : carrierPrivilegesListeners) { + verify(mTelephonyManagerShim) + .removeCarrierPrivilegesListener(eq(carrierPrivilegesListener)); + } + + // Expect a new CarrierPrivilegesListener to have been registered for slot 0, and none other + // (2 previously registered during startup, for slots 0 & 1) + verify(mTelephonyManagerShim, times(3)) + .addCarrierPrivilegesListener(anyInt(), any(), any()); + verify(mTelephonyManagerShim, times(2)) + .addCarrierPrivilegesListener(eq(0), any(), any()); + + final TelephonyNetworkSpecifier telephonyNetworkSpecifier = + new TelephonyNetworkSpecifier(0); + final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder(); + networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR); + networkRequestBuilder.setNetworkSpecifier(telephonyNetworkSpecifier); + assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest( + mCarrierConfigPkgUid, networkRequestBuilder.build())); + assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest( + mCarrierConfigPkgUid + 1, networkRequestBuilder.build())); + } + + @Test + public void testOnCarrierPrivilegesChanged() throws Exception { + final TelephonyManagerShim.CarrierPrivilegesListenerShim listener = + getCarrierPrivilegesListeners().get(0); + + final TelephonyNetworkSpecifier telephonyNetworkSpecifier = + new TelephonyNetworkSpecifier(0); + final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder(); + networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR); + networkRequestBuilder.setNetworkSpecifier(telephonyNetworkSpecifier); + + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.uid = mCarrierConfigPkgUid + 1; + doReturn(applicationInfo).when(mPackageManager) + .getApplicationInfo(eq(mTestPkg), anyInt()); + listener.onCarrierPrivilegesChanged(Collections.emptyList(), new int[] {}); + + assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest( + mCarrierConfigPkgUid, networkRequestBuilder.build())); + assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest( + mCarrierConfigPkgUid + 1, networkRequestBuilder.build())); + } +}