diff --git a/core/java/android/net/vcn/IVcnStatusCallback.aidl b/core/java/android/net/vcn/IVcnStatusCallback.aidl index a7386718d5ae58220b376cd1fb197cd86db22334..555e9b5883e839bfbadfa86cf28f2630c488a0a3 100644 --- a/core/java/android/net/vcn/IVcnStatusCallback.aidl +++ b/core/java/android/net/vcn/IVcnStatusCallback.aidl @@ -19,4 +19,9 @@ package android.net.vcn; /** @hide */ interface IVcnStatusCallback { void onEnteredSafeMode(); + void onGatewayConnectionError( + in int[] gatewayNetworkCapabilities, + int errorCode, + in String exceptionClass, + in String exceptionMessage); } \ 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 aed64de52cd02388f2766487137682086f233809..aea0ea988f5098d98fc87536124abcd3283496c1 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -17,7 +17,9 @@ package android.net.vcn; import static java.util.Objects.requireNonNull; +import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.content.Context; @@ -32,6 +34,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -262,6 +266,42 @@ public class VcnManager { } } + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + VCN_ERROR_CODE_INTERNAL_ERROR, + VCN_ERROR_CODE_CONFIG_ERROR, + VCN_ERROR_CODE_NETWORK_ERROR + }) + public @interface VcnErrorCode {} + + /** + * Value indicating that an internal failure occurred in this Gateway Connection. + * + * @hide + */ + public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0; + + /** + * Value indicating that an error with this Gateway Connection's configuration occurred. + * + * <p>For example, this error code will be returned after authentication failures. + * + * @hide + */ + public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1; + + /** + * Value indicating that a Network error occurred with this Gateway Connection. + * + * <p>For example, this error code will be returned if an underlying {@link android.net.Network} + * for this Gateway Connection is lost, or if an error occurs while resolving the connection + * endpoint address. + * + * @hide + */ + public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2; + // TODO: make VcnStatusCallback @SystemApi /** * VcnStatusCallback is the interface for Carrier apps to receive updates for their VCNs. @@ -285,6 +325,24 @@ public class VcnManager { * via {@link #setVcnConfig(ParcelUuid, VcnConfig)}. */ public abstract void onEnteredSafeMode(); + + /** + * Invoked when a VCN Gateway Connection corresponding to this callback's subscription + * encounters an error. + * + * @param networkCapabilities an array of underlying NetworkCapabilities for the Gateway + * Connection that encountered the error for identification purposes. These will be a + * sorted list with no duplicates, matching one of the {@link + * VcnGatewayConnectionConfig}s set in the {@link VcnConfig} for this subscription + * group. + * @param errorCode {@link VcnErrorCode} to indicate the error that occurred + * @param detail Throwable to provide additional information about the error, or {@code + * null} if none + */ + public abstract void onGatewayConnectionError( + @NonNull int[] networkCapabilities, + @VcnErrorCode int errorCode, + @Nullable Throwable detail); } /** @@ -385,11 +443,12 @@ public class VcnManager { * * @hide */ - private class VcnStatusCallbackBinder extends IVcnStatusCallback.Stub { + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static class VcnStatusCallbackBinder extends IVcnStatusCallback.Stub { @NonNull private final Executor mExecutor; @NonNull private final VcnStatusCallback mCallback; - private VcnStatusCallbackBinder( + public VcnStatusCallbackBinder( @NonNull Executor executor, @NonNull VcnStatusCallback callback) { mExecutor = executor; mCallback = callback; @@ -400,5 +459,36 @@ public class VcnManager { Binder.withCleanCallingIdentity( () -> mExecutor.execute(() -> mCallback.onEnteredSafeMode())); } + + // TODO(b/180521637): use ServiceSpecificException for safer Exception 'parceling' + @Override + public void onGatewayConnectionError( + @NonNull int[] networkCapabilities, + @VcnErrorCode int errorCode, + @Nullable String exceptionClass, + @Nullable String exceptionMessage) { + final Throwable cause = createThrowableByClassName(exceptionClass, exceptionMessage); + + Binder.withCleanCallingIdentity( + () -> + mExecutor.execute( + () -> + mCallback.onGatewayConnectionError( + networkCapabilities, errorCode, cause))); + } + + private static Throwable createThrowableByClassName( + @Nullable String className, @Nullable String message) { + if (className == null) { + return null; + } + + try { + Class<?> c = Class.forName(className); + return (Throwable) c.getConstructor(String.class).newInstance(message); + } catch (ReflectiveOperationException | ClassCastException e) { + return new RuntimeException(className + ": " + message); + } + } } } diff --git a/tests/vcn/java/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java index 1a90fc319bce242f51bb613492abab1b65927349..708767605508b042c1b1f06a92582b31e107b5f2 100644 --- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java +++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -33,6 +34,7 @@ import android.content.Context; import android.net.LinkProperties; import android.net.NetworkCapabilities; import android.net.vcn.VcnManager.VcnStatusCallback; +import android.net.vcn.VcnManager.VcnStatusCallbackBinder; import android.net.vcn.VcnManager.VcnUnderlyingNetworkPolicyListener; import android.os.ParcelUuid; @@ -40,11 +42,15 @@ import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; +import java.net.UnknownHostException; 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 int[] UNDERLYING_NETWORK_CAPABILITIES = { + NetworkCapabilities.NET_CAPABILITY_IMS, NetworkCapabilities.NET_CAPABILITY_INTERNET + }; private static final Executor INLINE_EXECUTOR = Runnable::run; private IVcnManagementService mMockVcnManagementService; @@ -144,14 +150,8 @@ public class VcnManagerTest { public void testRegisterVcnStatusCallback() throws Exception { mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback); - ArgumentCaptor<IVcnStatusCallback> captor = - ArgumentCaptor.forClass(IVcnStatusCallback.class); verify(mMockVcnManagementService) - .registerVcnStatusCallback(eq(SUB_GROUP), captor.capture(), any()); - - IVcnStatusCallback callbackWrapper = captor.getValue(); - callbackWrapper.onEnteredSafeMode(); - verify(mMockStatusCallback).onEnteredSafeMode(); + .registerVcnStatusCallback(eq(SUB_GROUP), notNull(), any()); } @Test(expected = IllegalStateException.class) @@ -195,4 +195,24 @@ public class VcnManagerTest { public void testUnregisterNullVcnStatusCallback() throws Exception { mVcnManager.unregisterVcnStatusCallback(null); } + + @Test + public void testVcnStatusCallbackBinder() throws Exception { + IVcnStatusCallback cbBinder = + new VcnStatusCallbackBinder(INLINE_EXECUTOR, mMockStatusCallback); + + cbBinder.onEnteredSafeMode(); + verify(mMockStatusCallback).onEnteredSafeMode(); + + cbBinder.onGatewayConnectionError( + UNDERLYING_NETWORK_CAPABILITIES, + VcnManager.VCN_ERROR_CODE_NETWORK_ERROR, + "java.net.UnknownHostException", + "exception_message"); + verify(mMockStatusCallback) + .onGatewayConnectionError( + eq(UNDERLYING_NETWORK_CAPABILITIES), + eq(VcnManager.VCN_ERROR_CODE_NETWORK_ERROR), + any(UnknownHostException.class)); + } }