diff --git a/core/java/android/net/vcn/IVcnManagementService.aidl b/core/java/android/net/vcn/IVcnManagementService.aidl index 4f293eeb3c3b6..6a3cb42ed75df 100644 --- a/core/java/android/net/vcn/IVcnManagementService.aidl +++ b/core/java/android/net/vcn/IVcnManagementService.aidl @@ -18,6 +18,7 @@ package android.net.vcn; import android.net.LinkProperties; import android.net.NetworkCapabilities; +import android.net.vcn.IVcnStatusCallback; import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; import android.net.vcn.VcnConfig; import android.net.vcn.VcnUnderlyingNetworkPolicy; @@ -33,4 +34,7 @@ interface IVcnManagementService { void addVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener); void removeVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener); VcnUnderlyingNetworkPolicy getUnderlyingNetworkPolicy(in NetworkCapabilities nc, in LinkProperties lp); + + void registerVcnStatusCallback(in ParcelUuid subscriptionGroup, in IVcnStatusCallback callback, in String opPkgName); + void unregisterVcnStatusCallback(in IVcnStatusCallback callback); } diff --git a/core/java/android/net/vcn/IVcnStatusCallback.aidl b/core/java/android/net/vcn/IVcnStatusCallback.aidl new file mode 100644 index 0000000000000..a7386718d5ae5 --- /dev/null +++ b/core/java/android/net/vcn/IVcnStatusCallback.aidl @@ -0,0 +1,22 @@ +/* + * Copyright 2021, 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 android.net.vcn; + +/** @hide */ +interface IVcnStatusCallback { + void onEnteredSafeMode(); +} \ No newline at end of file diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index 1a38338c26aa5..4d110d0fbf7ee 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -23,6 +23,7 @@ import android.annotation.SystemService; import android.content.Context; import android.net.LinkProperties; import android.net.NetworkCapabilities; +import android.os.Binder; import android.os.ParcelUuid; import android.os.RemoteException; import android.os.ServiceSpecificException; @@ -267,6 +268,100 @@ public class VcnManager { } } + // TODO: make VcnStatusCallback @SystemApi + /** + * VcnStatusCallback is the interface for Carrier apps to receive updates for their VCNs. + * + *

VcnStatusCallbacks may be registered before {@link VcnConfig}s are provided for a + * subscription group. + * + * @hide + */ + public abstract static class VcnStatusCallback { + private VcnStatusCallbackBinder mCbBinder; + + /** + * Invoked when the VCN for this Callback's subscription group enters safe mode. + * + *

A VCN will be put into safe mode if any of the gateway connections were unable to + * establish a connection within a system-determined timeout (while underlying networks were + * available). + * + *

A VCN-configuring app may opt to exit safe mode by (re)setting the VCN configuration + * via {@link #setVcnConfig(ParcelUuid, VcnConfig)}. + */ + public abstract void onEnteredSafeMode(); + } + + /** + * Registers the given callback to receive status updates for the specified subscription. + * + *

Callbacks can be registered for a subscription before {@link VcnConfig}s are set for it. + * + *

A {@link VcnStatusCallback} may only be registered for one subscription at a time. {@link + * VcnStatusCallback}s may be reused once unregistered. + * + *

A {@link VcnStatusCallback} will only be invoked if the registering package has carrier + * privileges for the specified subscription at the time of invocation. + * + * @param subscriptionGroup The subscription group to match for callbacks + * @param executor The {@link Executor} to be used for invoking callbacks + * @param callback The VcnStatusCallback to be registered + * @throws IllegalStateException if callback is currently registered with VcnManager + * @hide + */ + public void registerVcnStatusCallback( + @NonNull ParcelUuid subscriptionGroup, + @NonNull Executor executor, + @NonNull VcnStatusCallback callback) { + requireNonNull(subscriptionGroup, "subscriptionGroup must not be null"); + requireNonNull(executor, "executor must not be null"); + requireNonNull(callback, "callback must not be null"); + + synchronized (callback) { + if (callback.mCbBinder != null) { + throw new IllegalStateException("callback is already registered with VcnManager"); + } + callback.mCbBinder = new VcnStatusCallbackBinder(executor, callback); + + try { + mService.registerVcnStatusCallback( + subscriptionGroup, callback.mCbBinder, mContext.getOpPackageName()); + } catch (RemoteException e) { + callback.mCbBinder = null; + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Unregisters the given callback. + * + *

Once unregistered, the callback will stop receiving status updates for the subscription it + * was registered with. + * + * @param callback The callback to be unregistered + * @hide + */ + public void unregisterVcnStatusCallback(@NonNull VcnStatusCallback callback) { + requireNonNull(callback, "callback must not be null"); + + synchronized (callback) { + if (callback.mCbBinder == null) { + // no Binder attached to this callback, so it's not currently registered + return; + } + + try { + mService.unregisterVcnStatusCallback(callback.mCbBinder); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } finally { + callback.mCbBinder = null; + } + } + } + /** * Binder wrapper for added VcnUnderlyingNetworkPolicyListeners to receive signals from System * Server. @@ -286,7 +381,30 @@ public class VcnManager { @Override public void onPolicyChanged() { - mExecutor.execute(() -> mListener.onPolicyChanged()); + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> mListener.onPolicyChanged())); + } + } + + /** + * Binder wrapper for VcnStatusCallbacks to receive signals from VcnManagementService. + * + * @hide + */ + private class VcnStatusCallbackBinder extends IVcnStatusCallback.Stub { + @NonNull private final Executor mExecutor; + @NonNull private final VcnStatusCallback mCallback; + + private VcnStatusCallbackBinder( + @NonNull Executor executor, @NonNull VcnStatusCallback callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void onEnteredSafeMode() { + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> mCallback.onEnteredSafeMode())); } } } diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 27210daac2416..ed7f7837a7b0c 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -29,6 +29,7 @@ import android.net.LinkProperties; import android.net.NetworkCapabilities; import android.net.TelephonyNetworkSpecifier; import android.net.vcn.IVcnManagementService; +import android.net.vcn.IVcnStatusCallback; import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; import android.net.vcn.VcnConfig; import android.net.vcn.VcnUnderlyingNetworkPolicy; @@ -124,6 +125,7 @@ import java.util.concurrent.TimeUnit; * * @hide */ +// TODO(b/180451994): ensure all incoming + outgoing calls have a cleared calling identity public class VcnManagementService extends IVcnManagementService.Stub { @NonNull private static final String TAG = VcnManagementService.class.getSimpleName(); @@ -169,6 +171,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { private final Map mRegisteredPolicyListeners = new ArrayMap<>(); + @GuardedBy("mLock") + @NonNull + private final Map mRegisteredStatusCallbacks = new ArrayMap<>(); + @VisibleForTesting(visibility = Visibility.PRIVATE) VcnManagementService(@NonNull Context context, @NonNull Dependencies deps) { mContext = requireNonNull(context, "Missing context"); @@ -293,8 +299,8 @@ public class VcnManagementService extends IVcnManagementService.Stub { @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config, @NonNull TelephonySubscriptionSnapshot snapshot, - @NonNull VcnSafemodeCallback safemodeCallback) { - return new Vcn(vcnContext, subscriptionGroup, config, snapshot, safemodeCallback); + @NonNull VcnSafeModeCallback safeModeCallback) { + return new Vcn(vcnContext, subscriptionGroup, config, snapshot, safeModeCallback); } /** Gets the subId indicated by the given {@link WifiInfo}. */ @@ -440,12 +446,12 @@ public class VcnManagementService extends IVcnManagementService.Stub { // TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active // VCN. - final VcnSafemodeCallbackImpl safemodeCallback = - new VcnSafemodeCallbackImpl(subscriptionGroup); + final VcnSafeModeCallbackImpl safeModeCallback = + new VcnSafeModeCallbackImpl(subscriptionGroup); final Vcn newInstance = mDeps.newVcn( - mVcnContext, subscriptionGroup, config, mLastSnapshot, safemodeCallback); + mVcnContext, subscriptionGroup, config, mLastSnapshot, safeModeCallback); mVcns.put(subscriptionGroup, newInstance); // Now that a new VCN has started, notify all registered listeners to refresh their @@ -551,6 +557,14 @@ public class VcnManagementService extends IVcnManagementService.Stub { } } + /** Get current VcnStatusCallbacks for testing purposes. */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public Map getAllStatusCallbacks() { + synchronized (mLock) { + return Collections.unmodifiableMap(mRegisteredStatusCallbacks); + } + } + /** Binder death recipient used to remove a registered policy listener. */ private class PolicyListenerBinderDeath implements Binder.DeathRecipient { @NonNull private final IVcnUnderlyingNetworkPolicyListener mListener; @@ -672,22 +686,109 @@ public class VcnManagementService extends IVcnManagementService.Stub { return new VcnUnderlyingNetworkPolicy(false /* isTearDownRequested */, networkCapabilities); } - /** Callback for signalling when a Vcn has entered Safemode. */ - public interface VcnSafemodeCallback { - /** Called by a Vcn to signal that it has entered Safemode. */ - void onEnteredSafemode(); + /** Binder death recipient used to remove registered VcnStatusCallbacks. */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + class VcnStatusCallbackInfo implements Binder.DeathRecipient { + @NonNull final ParcelUuid mSubGroup; + @NonNull final IVcnStatusCallback mCallback; + @NonNull final String mPkgName; + final int mUid; + + private VcnStatusCallbackInfo( + @NonNull ParcelUuid subGroup, + @NonNull IVcnStatusCallback callback, + @NonNull String pkgName, + int uid) { + mSubGroup = subGroup; + mCallback = callback; + mPkgName = pkgName; + mUid = uid; + } + + @Override + public void binderDied() { + Log.e(TAG, "app died without unregistering VcnStatusCallback"); + unregisterVcnStatusCallback(mCallback); + } } - /** VcnSafemodeCallback is used by Vcns to notify VcnManagementService on entering Safemode. */ - private class VcnSafemodeCallbackImpl implements VcnSafemodeCallback { + /** Registers the provided callback for receiving VCN status updates. */ + @Override + public void registerVcnStatusCallback( + @NonNull ParcelUuid subGroup, + @NonNull IVcnStatusCallback callback, + @NonNull String opPkgName) { + final int callingUid = mDeps.getBinderCallingUid(); + final long identity = Binder.clearCallingIdentity(); + try { + requireNonNull(subGroup, "subGroup must not be null"); + requireNonNull(callback, "callback must not be null"); + requireNonNull(opPkgName, "opPkgName must not be null"); + + mContext.getSystemService(AppOpsManager.class).checkPackage(callingUid, opPkgName); + + final IBinder cbBinder = callback.asBinder(); + final VcnStatusCallbackInfo cbInfo = + new VcnStatusCallbackInfo( + subGroup, callback, opPkgName, mDeps.getBinderCallingUid()); + + try { + cbBinder.linkToDeath(cbInfo, 0 /* flags */); + } catch (RemoteException e) { + // Remote binder already died - don't add to mRegisteredStatusCallbacks and exit + return; + } + + synchronized (mLock) { + if (mRegisteredStatusCallbacks.containsKey(cbBinder)) { + throw new IllegalStateException( + "Attempting to register a callback that is already in use"); + } + + mRegisteredStatusCallbacks.put(cbBinder, cbInfo); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** Unregisters the provided callback from receiving future VCN status updates. */ + @Override + public void unregisterVcnStatusCallback(@NonNull IVcnStatusCallback callback) { + final long identity = Binder.clearCallingIdentity(); + try { + requireNonNull(callback, "callback must not be null"); + + final IBinder cbBinder = callback.asBinder(); + synchronized (mLock) { + VcnStatusCallbackInfo cbInfo = mRegisteredStatusCallbacks.remove(cbBinder); + + if (cbInfo != null) { + cbBinder.unlinkToDeath(cbInfo, 0 /* flags */); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + // TODO(b/180452282): Make name more generic and implement directly with VcnManagementService + /** Callback for signalling when a Vcn has entered safe mode. */ + public interface VcnSafeModeCallback { + /** Called by a Vcn to signal that it has entered safe mode. */ + void onEnteredSafeMode(); + } + + /** VcnSafeModeCallback is used by Vcns to notify VcnManagementService on entering safe mode. */ + private class VcnSafeModeCallbackImpl implements VcnSafeModeCallback { @NonNull private final ParcelUuid mSubGroup; - private VcnSafemodeCallbackImpl(@NonNull final ParcelUuid subGroup) { + private VcnSafeModeCallbackImpl(@NonNull final ParcelUuid subGroup) { mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup"); } @Override - public void onEnteredSafemode() { + public void onEnteredSafeMode() { synchronized (mLock) { // Ignore if this subscription group doesn't exist anymore if (!mVcns.containsKey(mSubGroup)) { diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java index 3726407211d5c..d4374a453f333 100644 --- a/services/core/java/com/android/server/vcn/Vcn.java +++ b/services/core/java/com/android/server/vcn/Vcn.java @@ -30,7 +30,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; -import com.android.server.VcnManagementService.VcnSafemodeCallback; +import com.android.server.VcnManagementService.VcnSafeModeCallback; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import java.util.Collections; @@ -97,7 +97,7 @@ public class Vcn extends Handler { @NonNull private final ParcelUuid mSubscriptionGroup; @NonNull private final Dependencies mDeps; @NonNull private final VcnNetworkRequestListener mRequestListener; - @NonNull private final VcnSafemodeCallback mVcnSafemodeCallback; + @NonNull private final VcnSafeModeCallback mVcnSafeModeCallback; @NonNull private final Map mVcnGatewayConnections = @@ -125,13 +125,13 @@ public class Vcn extends Handler { @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config, @NonNull TelephonySubscriptionSnapshot snapshot, - @NonNull VcnSafemodeCallback vcnSafemodeCallback) { + @NonNull VcnSafeModeCallback vcnSafeModeCallback) { this( vcnContext, subscriptionGroup, config, snapshot, - vcnSafemodeCallback, + vcnSafeModeCallback, new Dependencies()); } @@ -141,13 +141,13 @@ public class Vcn extends Handler { @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config, @NonNull TelephonySubscriptionSnapshot snapshot, - @NonNull VcnSafemodeCallback vcnSafemodeCallback, + @NonNull VcnSafeModeCallback vcnSafeModeCallback, @NonNull Dependencies deps) { super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper()); mVcnContext = vcnContext; mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); - mVcnSafemodeCallback = - Objects.requireNonNull(vcnSafemodeCallback, "Missing vcnSafemodeCallback"); + mVcnSafeModeCallback = + Objects.requireNonNull(vcnSafeModeCallback, "Missing vcnSafeModeCallback"); mDeps = Objects.requireNonNull(deps, "Missing deps"); mRequestListener = new VcnNetworkRequestListener(); @@ -246,7 +246,7 @@ public class Vcn extends Handler { private void handleEnterSafemode() { handleTeardown(); - mVcnSafemodeCallback.onEnteredSafemode(); + mVcnSafeModeCallback.onEnteredSafeMode(); } private void handleNetworkRequested( diff --git a/tests/vcn/java/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java index 7dada9d1b6d46..1a90fc319bce2 100644 --- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java +++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java @@ -26,24 +26,30 @@ import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.content.Context; import android.net.LinkProperties; import android.net.NetworkCapabilities; +import android.net.vcn.VcnManager.VcnStatusCallback; import android.net.vcn.VcnManager.VcnUnderlyingNetworkPolicyListener; +import android.os.ParcelUuid; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; +import java.util.UUID; import java.util.concurrent.Executor; public class VcnManagerTest { + private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0)); private static final Executor INLINE_EXECUTOR = Runnable::run; private IVcnManagementService mMockVcnManagementService; private VcnUnderlyingNetworkPolicyListener mMockPolicyListener; + private VcnStatusCallback mMockStatusCallback; private Context mContext; private VcnManager mVcnManager; @@ -52,6 +58,7 @@ public class VcnManagerTest { public void setUp() { mMockVcnManagementService = mock(IVcnManagementService.class); mMockPolicyListener = mock(VcnUnderlyingNetworkPolicyListener.class); + mMockStatusCallback = mock(VcnStatusCallback.class); mContext = getContext(); mVcnManager = new VcnManager(mContext, mMockVcnManagementService); @@ -132,4 +139,60 @@ public class VcnManagerTest { public void testGetUnderlyingNetworkPolicyNullLinkProperties() throws Exception { mVcnManager.getUnderlyingNetworkPolicy(new NetworkCapabilities(), null); } + + @Test + public void testRegisterVcnStatusCallback() throws Exception { + mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(IVcnStatusCallback.class); + verify(mMockVcnManagementService) + .registerVcnStatusCallback(eq(SUB_GROUP), captor.capture(), any()); + + IVcnStatusCallback callbackWrapper = captor.getValue(); + callbackWrapper.onEnteredSafeMode(); + verify(mMockStatusCallback).onEnteredSafeMode(); + } + + @Test(expected = IllegalStateException.class) + public void testRegisterVcnStatusCallbackAlreadyRegistered() throws Exception { + mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback); + mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback); + } + + @Test(expected = NullPointerException.class) + public void testRegisterVcnStatusCallbackNullSubscriptionGroup() throws Exception { + mVcnManager.registerVcnStatusCallback(null, INLINE_EXECUTOR, mMockStatusCallback); + } + + @Test(expected = NullPointerException.class) + public void testRegisterVcnStatusCallbackNullExecutor() throws Exception { + mVcnManager.registerVcnStatusCallback(SUB_GROUP, null, mMockStatusCallback); + } + + @Test(expected = NullPointerException.class) + public void testRegisterVcnStatusCallbackNullCallback() throws Exception { + mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, null); + } + + @Test + public void testUnregisterVcnStatusCallback() throws Exception { + mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback); + + mVcnManager.unregisterVcnStatusCallback(mMockStatusCallback); + + verify(mMockVcnManagementService).unregisterVcnStatusCallback(any()); + } + + @Test + public void testUnregisterUnknownVcnStatusCallback() throws Exception { + mVcnManager.unregisterVcnStatusCallback(mMockStatusCallback); + + verifyNoMoreInteractions(mMockVcnManagementService); + } + + @Test(expected = NullPointerException.class) + public void testUnregisterNullVcnStatusCallback() throws Exception { + mVcnManager.unregisterVcnStatusCallback(null); + } } diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index c290bff188c1a..f258ab4465947 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -26,6 +26,7 @@ import static com.android.server.vcn.VcnTestUtils.setupSystemService; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -52,6 +53,7 @@ import android.net.LinkProperties; import android.net.NetworkCapabilities; import android.net.NetworkCapabilities.Transport; import android.net.TelephonyNetworkSpecifier; +import android.net.vcn.IVcnStatusCallback; import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; import android.net.vcn.VcnConfig; import android.net.vcn.VcnConfigTest; @@ -70,7 +72,8 @@ import android.telephony.TelephonyManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.server.VcnManagementService.VcnSafemodeCallback; +import com.android.server.VcnManagementService.VcnSafeModeCallback; +import com.android.server.VcnManagementService.VcnStatusCallbackInfo; import com.android.server.vcn.TelephonySubscriptionTracker; import com.android.server.vcn.Vcn; import com.android.server.vcn.VcnContext; @@ -148,13 +151,14 @@ public class VcnManagementServiceTest { private final TelephonySubscriptionTracker mSubscriptionTracker = mock(TelephonySubscriptionTracker.class); - private final ArgumentCaptor mSafemodeCallbackCaptor = - ArgumentCaptor.forClass(VcnSafemodeCallback.class); + private final ArgumentCaptor mSafeModeCallbackCaptor = + ArgumentCaptor.forClass(VcnSafeModeCallback.class); private final VcnManagementService mVcnMgmtSvc; private final IVcnUnderlyingNetworkPolicyListener mMockPolicyListener = mock(IVcnUnderlyingNetworkPolicyListener.class); + private final IVcnStatusCallback mMockStatusCallback = mock(IVcnStatusCallback.class); private final IBinder mMockIBinder = mock(IBinder.class); public VcnManagementServiceTest() throws Exception { @@ -206,6 +210,7 @@ public class VcnManagementServiceTest { mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps); doReturn(mMockIBinder).when(mMockPolicyListener).asBinder(); + doReturn(mMockIBinder).when(mMockStatusCallback).asBinder(); // Make sure the profiles are loaded. mTestLooper.dispatchAll(); @@ -708,7 +713,7 @@ public class VcnManagementServiceTest { } @Test - public void testVcnSafemodeCallbackOnEnteredSafemode() throws Exception { + public void testVcnSafeModeCallbackOnEnteredSafeMode() throws Exception { TelephonySubscriptionSnapshot snapshot = triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1)); verify(mMockDeps) @@ -717,14 +722,60 @@ public class VcnManagementServiceTest { eq(TEST_UUID_1), eq(TEST_VCN_CONFIG), eq(snapshot), - mSafemodeCallbackCaptor.capture()); + mSafeModeCallbackCaptor.capture()); mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); - VcnSafemodeCallback safemodeCallback = mSafemodeCallbackCaptor.getValue(); - safemodeCallback.onEnteredSafemode(); + VcnSafeModeCallback safeModeCallback = mSafeModeCallbackCaptor.getValue(); + safeModeCallback.onEnteredSafeMode(); assertFalse(mVcnMgmtSvc.getAllVcns().get(TEST_UUID_1).isActive()); verify(mMockPolicyListener).onPolicyChanged(); } + + @Test + public void testRegisterVcnStatusCallback() throws Exception { + mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); + + Map callbacks = mVcnMgmtSvc.getAllStatusCallbacks(); + VcnStatusCallbackInfo cbInfo = callbacks.get(mMockIBinder); + + assertNotNull(cbInfo); + assertEquals(TEST_UUID_1, cbInfo.mSubGroup); + assertEquals(mMockStatusCallback, cbInfo.mCallback); + assertEquals(TEST_PACKAGE_NAME, cbInfo.mPkgName); + assertEquals(TEST_UID, cbInfo.mUid); + verify(mMockIBinder).linkToDeath(eq(cbInfo), anyInt()); + } + + @Test(expected = IllegalStateException.class) + public void testRegisterVcnStatusCallbackDuplicate() { + mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); + mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); + } + + @Test + public void testUnregisterVcnStatusCallback() { + mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); + Map callbacks = mVcnMgmtSvc.getAllStatusCallbacks(); + VcnStatusCallbackInfo cbInfo = callbacks.get(mMockIBinder); + + mVcnMgmtSvc.unregisterVcnStatusCallback(mMockStatusCallback); + assertTrue(mVcnMgmtSvc.getAllStatusCallbacks().isEmpty()); + verify(mMockIBinder).unlinkToDeath(eq(cbInfo), anyInt()); + } + + @Test(expected = SecurityException.class) + public void testRegisterVcnStatusCallbackInvalidPackage() { + doThrow(new SecurityException()).when(mAppOpsMgr).checkPackage(TEST_UID, TEST_PACKAGE_NAME); + + mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); + } + + @Test + public void testUnregisterVcnStatusCallbackNeverRegistered() { + mVcnMgmtSvc.unregisterVcnStatusCallback(mMockStatusCallback); + + assertTrue(mVcnMgmtSvc.getAllStatusCallbacks().isEmpty()); + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java index 66cbf84619ab0..1f510a2ac763b 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java @@ -34,7 +34,7 @@ import android.net.vcn.VcnGatewayConnectionConfigTest; import android.os.ParcelUuid; import android.os.test.TestLooper; -import com.android.server.VcnManagementService.VcnSafemodeCallback; +import com.android.server.VcnManagementService.VcnSafeModeCallback; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.Vcn.VcnGatewayStatusCallback; import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener; @@ -56,7 +56,7 @@ public class VcnTest { private VcnContext mVcnContext; private TelephonySubscriptionSnapshot mSubscriptionSnapshot; private VcnNetworkProvider mVcnNetworkProvider; - private VcnSafemodeCallback mVcnSafemodeCallback; + private VcnSafeModeCallback mVcnSafeModeCallback; private Vcn.Dependencies mDeps; private ArgumentCaptor mGatewayStatusCallbackCaptor; @@ -72,7 +72,7 @@ public class VcnTest { mVcnContext = mock(VcnContext.class); mSubscriptionSnapshot = mock(TelephonySubscriptionSnapshot.class); mVcnNetworkProvider = mock(VcnNetworkProvider.class); - mVcnSafemodeCallback = mock(VcnSafemodeCallback.class); + mVcnSafeModeCallback = mock(VcnSafeModeCallback.class); mDeps = mock(Vcn.Dependencies.class); mTestLooper = new TestLooper(); @@ -104,7 +104,7 @@ public class VcnTest { TEST_SUB_GROUP, mConfig, mSubscriptionSnapshot, - mVcnSafemodeCallback, + mVcnSafeModeCallback, mDeps); } @@ -148,7 +148,7 @@ public class VcnTest { } @Test - public void testGatewayEnteringSafemodeNotifiesVcn() { + public void testGatewayEnteringSafeModeNotifiesVcn() { final NetworkRequestListener requestListener = verifyAndGetRequestListener(); for (final int capability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) { startVcnGatewayWithCapabilities(requestListener, capability); @@ -168,7 +168,7 @@ public class VcnTest { any(), mGatewayStatusCallbackCaptor.capture()); - // Doesn't matter which callback this gets - any Gateway entering Safemode should shut down + // Doesn't matter which callback this gets - any Gateway entering safe mode should shut down // all Gateways final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue(); statusCallback.onEnteredSafemode(); @@ -178,6 +178,6 @@ public class VcnTest { verify(gatewayConnection).teardownAsynchronously(); } verify(mVcnNetworkProvider).unregisterListener(requestListener); - verify(mVcnSafemodeCallback).onEnteredSafemode(); + verify(mVcnSafeModeCallback).onEnteredSafeMode(); } }