2

In an Android app which uses AWS services, if I deregister a registered ConnectivityManager.NetworkCallback, the app can no longer contact AWS services. I am uncertain why this is occurring, or how to contact the AWS services again. Currently, the only way to reconnect to AWS is to terminate the app and restart it.

To elaborate, one function of the app is to connect the user's Android device to a different WiFi network hotspot for the purposes of setting up an IoT device. At this moment of use, the user would be logged in using AWS's Cognito service. Because how an app can connect to WiFi was changed starting with API level 29, this associated code is only invoked on such devices and the problem is isolated to said devices. Here is the relevant snippet for how the connection is being created:

    NetworkRequest request =
        new NetworkRequest.Builder()
                .addTransportType(TRANSPORT_WIFI)
                .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                .setNetworkSpecifier(specifier)
                .build();

    ConnectivityManager connectivityManager = (ConnectivityManager) viewModel
            .getApplication()
            .getSystemService(Context.CONNECTIVITY_SERVICE);

    ConnectivityManager.NetworkCallback networkCallback = viewModel.getNetworkCallback();

    if (connectivityManager != null && request != null) {
        // Request the network.
        connectivityManager.requestNetwork(request, networkCallback, TIMEOUT_AMOUNT);
    }

Unregistering the callback occurs if the setup is completed, if an error is encountered, or if the user decides to navigate backwards out of this setup. Regardless, this is the code used to accomplish unregistering:

    ConnectivityManager connectivityManager = (ConnectivityManager) viewModel
        .getApplication()
        .getApplicationContext()
        .getSystemService(Context.CONNECTIVITY_SERVICE);
    WiFiNetworkCallback networkCallback = viewModel.getNetworkCallback();
    if (connectivityManager != null) {
        try {
            connectivityManager.unregisterNetworkCallback(networkCallback);
        } catch (IllegalArgumentException e) {
            // Network is already unregistered.
            e.printStackTrace();
        }
    }

This code successfully invokes in all circumstances. At this point, the user is no longer connected to the WiFi hotspot. In my testing, I would now be connected back to my home WiFi with my mobile network available too. However, the app can no longer contact AWS. Considering that my device is still connected to a WiFi network AND my mobile network, I would not expect this outcome. Here is an example stack trace for trying to contact Cognito (although the same sort of error occurs if DynamoDB is contacted, for example):

W/System.err: com.amazonaws.AmazonClientException: Unable to execute HTTP request: Unable to resolve host "cognito-idp.us-east-2.amazonaws.com": No address associated with hostname
W/System.err:     at com.amazonaws.http.AmazonHttpClient.executeHelper(AmazonHttpClient.java:456)
W/System.err:     at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:229)
W/System.err:     at com.amazonaws.services.cognitoidentityprovider.AmazonCognitoIdentityProviderClient.invoke(AmazonCognitoIdentityProviderClient.java:6329)
W/System.err:     at com.amazonaws.services.cognitoidentityprovider.AmazonCognitoIdentityProviderClient.getUser(AmazonCognitoIdentityProviderClient.java:4052)
W/System.err:     at com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUser.getUserDetailsInternal(CognitoUser.java:1487)
W/System.err:     at com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUser.access$700(CognitoUser.java:133)
W/System.err:     at com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUser$10.run(CognitoUser.java:1433)
W/System.err:     at java.lang.Thread.run(Thread.java:923)
W/System.err: Caused by: java.net.UnknownHostException: Unable to resolve host "cognito-idp.us-east-2.amazonaws.com": No address associated with hostname
W/System.err:     at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:124)
W/System.err:     at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:103)
W/System.err:     at java.net.InetAddress.getAllByName(InetAddress.java:1152)
W/System.err:     at com.android.okhttp.Dns$1.lookup(Dns.java:41)
W/System.err:     at com.android.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:178)
W/System.err:     at com.android.okhttp.internal.http.RouteSelector.nextProxy(RouteSelector.java:144)
W/System.err:     at com.android.okhttp.internal.http.RouteSelector.next(RouteSelector.java:86)
W/System.err:     at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:192)
W/System.err:     at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:144)
W/System.err:     at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:106)
W/System.err:     at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:400)
W/System.err:     at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:333)
W/System.err:     at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:465)
W/System.err:     at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:131)
W/System.err:     at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getOutputStream(HttpURLConnectionImpl.java:262)
W/System.err:     at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getOutputStream(DelegatingHttpsURLConnection.java:219)
W/System.err:     at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:30)
W/System.err:     at com.amazonaws.http.UrlHttpClient.writeContentToConnection(UrlHttpClient.java:162)
W/System.err:     at com.amazonaws.http.UrlHttpClient.execute(UrlHttpClient.java:75)
W/System.err:     at com.amazonaws.http.AmazonHttpClient.executeHelper(AmazonHttpClient.java:386)
W/System.err:   ... 7 more

I have isolated it to specifically when the app performs .unregisterNetworkCallback() to be the cause. If this is not invoked, then the app will not lose the ability to contact AWS.

Additionally, when attempting any AWS connection after unregistering, many of these statements will print into the log before the exception above is logged:

I/System.out: (HTTPLog)-Static: isSBSettingEnabled false

In my research, it seems to have something to do with Samsung devices. I am testing this on a Samsung Galaxy S10 Lite. I am not sure if this is related to the issue at hand.

Why is this occurring? Am I somehow using the ConnectivityManager.NetworkCallback incorrectly, either in registering or unregistering it? Is it an issue with AWS?

Flash103
  • 347
  • 4
  • 14

1 Answers1

0

I realized the issue involved a snippet of code outside of the code shared in this question. The NetworkCallback's onAvailable() is performed as such:

    @Override
    public void onAvailable(@NonNull Network network) {
        connectivityManager.bindProcessToNetwork(network);
    }

The reason we are calling .bindProcessToNetwork() is so that the app can still access the internet beyond just the WiFi network.

This is key to why the app is losing connection to AWS after calling .unregisterNetworkCallback(); it is being unregistered but it is still bound to the network we just unregistered. Therefore, when one goes to unregister the NetworkCallback, be sure to first invoke .bindProcessToNetwork() and pass null to clear out the binding:

    ConnectivityManager connectivityManager = (ConnectivityManager) viewModel
        .getApplication()
        .getApplicationContext()
        .getSystemService(Context.CONNECTIVITY_SERVICE);
    WiFiNetworkCallback networkCallback = viewModel.getNetworkCallback();
    if (connectivityManager != null) {
        try {
            // Remove the app from the network callback first before deregistering it. Otherwise,
            // the app will be stuck on the deregistered network (giving it no connectivity).
            connectivityManager.bindProcessToNetwork(null);
            connectivityManager.unregisterNetworkCallback(networkCallback);
        } catch (IllegalArgumentException e) {
            // Network is already unregistered.
            e.printStackTrace();
        }
    }

I came to this answer by reading this answer on a different question. It gives a great overview of how the whole WiFi connection/management process works for Android API 29 and higher, including this final part of disconnecting and allowing the user to continue with the app experience on their normal WiFi or cellular network.

Flash103
  • 347
  • 4
  • 14