From 59881b17b43764fd0470adc49716648dd1c22ac1 Mon Sep 17 00:00:00 2001 From: Cody Kesting Date: Sun, 7 Feb 2021 12:40:53 -0800 Subject: [PATCH] Notify status callbacks when a VCN enters Safemode. This CL updates VcnManagementService to notify VcnStatusCallbacks when the VCN for their specified subscription group enters Safemode. In order to be notified, the registering app must also have permissions for the specified permission. Bug: 163433613 Test: atest FrameworksVcnTests Change-Id: I3242ad0ee1dc406aef56253f884c2544a994869e --- .../android/server/VcnManagementService.java | 32 +++++++ .../server/VcnManagementServiceTest.java | 89 +++++++++++++++++-- .../java/com/android/server/vcn/VcnTest.java | 1 + 3 files changed, 116 insertions(+), 6 deletions(-) diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index ed7f7837a7b0c..ed4e1d951b431 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -22,6 +22,7 @@ import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubsc import static java.util.Objects.requireNonNull; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.AppOpsManager; import android.content.Context; import android.net.ConnectivityManager; @@ -55,6 +56,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.internal.util.LocationPermissionChecker; import com.android.server.vcn.TelephonySubscriptionTracker; import com.android.server.vcn.Vcn; import com.android.server.vcn.VcnContext; @@ -149,6 +151,9 @@ public class VcnManagementService extends IVcnManagementService.Stub { @NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker; @NonNull private final VcnContext mVcnContext; + /** Can only be assigned when {@link #systemReady()} is called, since it uses AppOpsManager. */ + @Nullable private LocationPermissionChecker mLocationPermissionChecker; + @GuardedBy("mLock") @NonNull private final Map mConfigs = new ArrayMap<>(); @@ -308,6 +313,11 @@ public class VcnManagementService extends IVcnManagementService.Stub { // TODO(b/178501049): use the subId indicated by WifiInfo#getSubscriptionId return SubscriptionManager.INVALID_SUBSCRIPTION_ID; } + + /** Creates a new LocationPermissionChecker for the provided Context. */ + public LocationPermissionChecker newLocationPermissionChecker(@NonNull Context context) { + return new LocationPermissionChecker(context); + } } /** Notifies the VcnManagementService that external dependencies can be set up. */ @@ -315,6 +325,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { mContext.getSystemService(ConnectivityManager.class) .registerNetworkProvider(mNetworkProvider); mTelephonySubscriptionTracker.register(); + mLocationPermissionChecker = mDeps.newLocationPermissionChecker(mVcnContext.getContext()); } private void enforcePrimaryUser() { @@ -796,6 +807,27 @@ public class VcnManagementService extends IVcnManagementService.Stub { } notifyAllPolicyListenersLocked(); + + // Notify all registered StatusCallbacks for this subGroup + for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) { + if (!mSubGroup.equals(cbInfo.mSubGroup)) { + continue; + } + if (!mLastSnapshot.packageHasPermissionsForSubscriptionGroup( + mSubGroup, cbInfo.mPkgName)) { + continue; + } + + if (!mLocationPermissionChecker.checkLocationPermission( + cbInfo.mPkgName, + "VcnStatusCallback" /* featureId */, + cbInfo.mUid, + null /* message */)) { + continue; + } + + Binder.withCleanCallingIdentity(() -> cbInfo.mCallback.onEnteredSafeMode()); + } } } } diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index f258ab4465947..124ec3056fb22 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -43,9 +43,11 @@ import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.NonNull; import android.app.AppOpsManager; import android.content.Context; import android.net.ConnectivityManager; @@ -72,6 +74,7 @@ import android.telephony.TelephonyManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.util.LocationPermissionChecker; import com.android.server.VcnManagementService.VcnSafeModeCallback; import com.android.server.VcnManagementService.VcnStatusCallbackInfo; import com.android.server.vcn.TelephonySubscriptionTracker; @@ -150,6 +153,8 @@ public class VcnManagementServiceTest { mock(PersistableBundleUtils.LockingReadWriteHelper.class); private final TelephonySubscriptionTracker mSubscriptionTracker = mock(TelephonySubscriptionTracker.class); + private final LocationPermissionChecker mLocationPermissionChecker = + mock(LocationPermissionChecker.class); private final ArgumentCaptor mSafeModeCallbackCaptor = ArgumentCaptor.forClass(VcnSafeModeCallback.class); @@ -175,6 +180,7 @@ public class VcnManagementServiceTest { doReturn(TEST_PACKAGE_NAME).when(mMockContext).getOpPackageName(); + doReturn(mMockContext).when(mVcnContext).getContext(); doReturn(mTestLooper.getLooper()).when(mMockDeps).getLooper(); doReturn(TEST_UID).when(mMockDeps).getBinderCallingUid(); doReturn(mVcnContext) @@ -192,6 +198,9 @@ public class VcnManagementServiceTest { doReturn(mConfigReadWriteHelper) .when(mMockDeps) .newPersistableBundleLockingReadWriteHelper(any()); + doReturn(mLocationPermissionChecker) + .when(mMockDeps) + .newLocationPermissionChecker(eq(mMockContext)); // Setup VCN instance generation doAnswer((invocation) -> { @@ -712,14 +721,13 @@ public class VcnManagementServiceTest { verify(mMockPolicyListener).onPolicyChanged(); } - @Test - public void testVcnSafeModeCallbackOnEnteredSafeMode() throws Exception { - TelephonySubscriptionSnapshot snapshot = - triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1)); + private void verifyVcnSafeModeCallback( + @NonNull ParcelUuid subGroup, @NonNull TelephonySubscriptionSnapshot snapshot) + throws Exception { verify(mMockDeps) .newVcn( eq(mVcnContext), - eq(TEST_UUID_1), + eq(subGroup), eq(TEST_VCN_CONFIG), eq(snapshot), mSafeModeCallbackCaptor.capture()); @@ -729,10 +737,79 @@ public class VcnManagementServiceTest { VcnSafeModeCallback safeModeCallback = mSafeModeCallbackCaptor.getValue(); safeModeCallback.onEnteredSafeMode(); - assertFalse(mVcnMgmtSvc.getAllVcns().get(TEST_UUID_1).isActive()); verify(mMockPolicyListener).onPolicyChanged(); } + @Test + public void testVcnSafeModeCallbackOnEnteredSafeMode() throws Exception { + TelephonySubscriptionSnapshot snapshot = + triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1)); + + verifyVcnSafeModeCallback(TEST_UUID_1, snapshot); + } + + private void triggerVcnStatusCallbackOnEnteredSafeMode( + @NonNull ParcelUuid subGroup, + @NonNull String pkgName, + int uid, + boolean hasPermissionsforSubGroup, + boolean hasLocationPermission) + throws Exception { + TelephonySubscriptionSnapshot snapshot = + triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(subGroup)); + + doReturn(hasPermissionsforSubGroup) + .when(snapshot) + .packageHasPermissionsForSubscriptionGroup(eq(subGroup), eq(pkgName)); + + doReturn(hasLocationPermission) + .when(mLocationPermissionChecker) + .checkLocationPermission(eq(pkgName), any(), eq(uid), any()); + + mVcnMgmtSvc.registerVcnStatusCallback(subGroup, mMockStatusCallback, pkgName); + + // Trigger systemReady() to set up LocationPermissionChecker + mVcnMgmtSvc.systemReady(); + + verifyVcnSafeModeCallback(subGroup, snapshot); + } + + @Test + public void testVcnStatusCallbackOnEnteredSafeModeWithCarrierPrivileges() throws Exception { + triggerVcnStatusCallbackOnEnteredSafeMode( + TEST_UUID_1, + TEST_PACKAGE_NAME, + TEST_UID, + true /* hasPermissionsforSubGroup */, + true /* hasLocationPermission */); + + verify(mMockStatusCallback, times(1)).onEnteredSafeMode(); + } + + @Test + public void testVcnStatusCallbackOnEnteredSafeModeWithoutCarrierPrivileges() throws Exception { + triggerVcnStatusCallbackOnEnteredSafeMode( + TEST_UUID_1, + TEST_PACKAGE_NAME, + TEST_UID, + false /* hasPermissionsforSubGroup */, + true /* hasLocationPermission */); + + verify(mMockStatusCallback, never()).onEnteredSafeMode(); + } + + @Test + public void testVcnStatusCallbackOnEnteredSafeModeWithoutLocationPermission() throws Exception { + triggerVcnStatusCallbackOnEnteredSafeMode( + TEST_UUID_1, + TEST_PACKAGE_NAME, + TEST_UID, + true /* hasPermissionsforSubGroup */, + false /* hasLocationPermission */); + + verify(mMockStatusCallback, never()).onEnteredSafeMode(); + } + @Test public void testRegisterVcnStatusCallback() throws Exception { mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java index 5b37c80bd3297..8e142c095ae3a 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java @@ -174,6 +174,7 @@ public class VcnTest { statusCallback.onEnteredSafeMode(); mTestLooper.dispatchAll(); + assertFalse(mVcn.isActive()); for (final VcnGatewayConnection gatewayConnection : gatewayConnections) { verify(gatewayConnection).teardownAsynchronously(); }