From f14145e37ae8fbe04333948aa5f207aeed9d9d5b Mon Sep 17 00:00:00 2001 From: Cody Kesting Date: Sat, 6 Feb 2021 16:48:45 -0800 Subject: [PATCH] Define VcnStatusCallback register/unregister. This CL defines VcnStatusCallbacks, which are callbacks used to register for status updates to a specific subscription group. These Callbacks may be registered with VcnManager at any time, but will only be invoked for the specifies subscription group and only if the registering app has carrier privileges for that subscription. Bug: 163433613 Test: atest FrameworksVcnTests Change-Id: Iefd284ae2d09676d195e2a12bf660be3596da59b --- .../net/vcn/IVcnManagementService.aidl | 4 + .../android/net/vcn/IVcnStatusCallback.aidl | 22 +++ core/java/android/net/vcn/VcnManager.java | 120 ++++++++++++++++- .../android/server/VcnManagementService.java | 127 ++++++++++++++++-- .../core/java/com/android/server/vcn/Vcn.java | 16 +-- .../java/android/net/vcn/VcnManagerTest.java | 63 +++++++++ .../server/VcnManagementServiceTest.java | 65 ++++++++- .../java/com/android/server/vcn/VcnTest.java | 14 +- 8 files changed, 395 insertions(+), 36 deletions(-) create mode 100644 core/java/android/net/vcn/IVcnStatusCallback.aidl 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(); } }