4

I have a big issue with users using my Android application on Samsung Galaxy S3/S4/Note2.

My application sends a GET request to retrieve content of "http://www.msftncsi.com/ncsi.txt" to check if Internet connection is working fine before opening a web form in a WebView. It works very fine most devices, anyway it has a weird behaviour on Samsung devices because it looks like it stores the request response for 10 minutes.

So, if I send this request when Internet access is down then I'll get same results during 10 minutes (false negatives) even if Internet access has been repaired. The contrary is true : if I send the request when Internet access is OK then I'll receive same response during 10 minutes (false positives) even if I disconnect my router from Internet.

I tried solutions from this thread (none worked) : How to prevent Android from returning a cached response to my HTTP Request?

Important note : I can't modify server's response headers.

Here is the code failing on Samsung devices only :

Logger.d(LOG_TAG, "Starting the thread checking internet access.");
boolean isInternetAccessAvailable = false;
boolean tryAgain = false;

do
{
    try
    {
        HttpClient httpClient = new DefaultHttpClient();

        HttpParams httpParams = httpClient.getParams();
        HttpConnectionParams.setConnectionTimeout(httpParams, mRequestTimeout);
        HttpConnectionParams.setSoTimeout(httpParams, mRequestTimeout);

        HttpClientParams.setRedirecting(httpParams, false);

        HttpGet httpGet = new HttpGet(CHECK_INTERNET_URL);

        HttpResponse httpResponse = httpClient.execute(httpGet);

        if (!mCheckInternetTask.isCancelled())
        {
            Logger.d(LOG_TAG, "Received server response. Start response content parsing.");

            StatusLine statusLine = httpResponse.getStatusLine();
            int statuscode = statusLine.getStatusCode();

            if (statuscode == HttpStatus.SC_OK)
            {
                HttpEntity entity = httpResponse.getEntity();

                if (entity != null)
                {
                    String resultString = null;
                    try
                    {
                        InputStream inputStream = null;
                        inputStream = entity.getContent();
                        resultString = convertStreamToString(inputStream);
                    }
                    catch (IllegalStateException e)
                    {
                        Logger.w(LOG_TAG, "IllegalStateException during HTTP entity conversion into String", e);
                    }
                    catch (IOException e)
                    {
                        Logger.w(LOG_TAG, "IOException during HTTP entity conversion into String", e);
                    }

                    if (resultString != null && resultString.equals("Microsoft NCSI"))
                    {
                        Logger.i(LOG_TAG, "Microsoft NCSI web content reachable: Internet OK.");
                        isInternetAccessAvailable = true;
                    }
                    else
                    {
                        Logger.i(LOG_TAG, "Microsoft NCSI web content not reachable: Internet KO.");
                    }
                }
                else
                {
                    Logger.e(LOG_TAG, "getInternetStatusFromOS() HttpEntity is null !!");
                }

                // Do no try again
                tryAgain = false;
            }
            else if (statuscode == HttpStatus.SC_MOVED_TEMPORARILY)
            {
                Logger.i(LOG_TAG, "Captive portal detected, Internet KO");
            }
            else if (!tryAgain)
            {
                Logger.w(LOG_TAG, "HTTP response code is " + statuscode + ", not 200, try to clear the DNS cache and try again");
                clearDnsCache();
                tryAgain = true;
            }
            else
            {
                Logger.i(LOG_TAG, "HTTP response code is " + statuscode + ", not 200: Internet KO.");

                // Do no try again
                tryAgain = false;
            }
        }
    }
    catch (ConnectTimeoutException e)
    {
        Logger.d(LOG_TAG, "ConnectTimeoutException caught: Internet KO.", e);
    }
    catch (SocketTimeoutException e)
    {
        tryAgain = true;
        Logger.d(LOG_TAG, "SocketTimeoutException caught: Internet KO.", e);
    }
    catch (ClientProtocolException e)
    {
        Logger.d(LOG_TAG, "ClientProtocolException caught: Internet KO.", e);
    }
    catch (IOException e)
    {
        Logger.d(LOG_TAG, "IOException caught: Internet KO.", e);
    }
}
while (tryAgain && !mCheckInternetTask.isCancelled());

if (mCheckInternetTask.isCancelled())
{
    Logger.d(LOG_TAG, "Thread interrupted (timed out).");
}
else
{
    Logger.d(LOG_TAG, "Check Internet access runnable finished, with result: " + isInternetAccessAvailable);
    setCheckInternetAccessResult(isInternetAccessAvailable);
}

I already sorted this problem on the Samsung Galaxy S2 using a hidden function to clear dns cache but it doesn't work anymore on Galaxy S3/S4/Note2 (and probably other Galaxy devices) :

/**
 * Try to clear the DNS cache, if necessary.<br/>
 * When starting the application, if the {@value #CHECK_INTERNET_URL} is not reachable, the DNS cache will
 * remember this status for at least 10 minutes. Any request to this URL will return with a 404 error
 * during this period.<br>
 * Two solutions can be found:
 * <ul>
 * <li>Using the <a
 * href="http://docs.oracle.com/javase/6/docs/technotes/guides/net/properties.html#nct">networkaddress
 * .cache.ttl</a> and the <a
 * href="http://docs.oracle.com/javase/6/docs/technotes/guides/net/properties.html#ncnt"
 * >networkaddress.cache.negative.ttl</a> constants.</li>
 * <li>Using the clearDnsCache from the <a href=
 * "https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/java/net/InetAddress.java"
 * >INetAddress</a> class source code</li>
 * </ul>
 * The first solution doesn't work on the Galaxy SII (the device on which the Internet access detection
 * problem appeared first). The second solutions needs to use a method that is not visible from the code:
 * we will have to use Reflection.
 * 
 * @return true if the clearDnsCache method could be found, and was called.
 */
private boolean clearDnsCache()
{
    boolean success = false;

    try
    {
        Method method = null;
        method = InetAddress.class.getMethod("clearDnsCache");

        if (method != null)
        {
            method.invoke(null);
            success = true;
        }
    }
    catch (SecurityException e)
    {
        Logger.d(LOG_TAG, "SecurityException when trying to call INetAddress#clearDnsCache()", e);
    }
    catch (NoSuchMethodException e)
    {
        Logger.d(LOG_TAG, "NoSuchMethodException when trying to call INetAddress#clearDnsCache()", e);
    }
    catch (IllegalArgumentException e)
    {
        Logger.d(LOG_TAG, "IllegalArgumentException when trying to call INetAddress#clearDnsCache()", e);
    }
    catch (IllegalAccessException e)
    {
        Logger.d(LOG_TAG, "IllegalAccessException when trying to call INetAddress#clearDnsCache()", e);
    }
    catch (InvocationTargetException e)
    {
        Logger.d(LOG_TAG, "InvocationTargetException when trying to call INetAddress#clearDnsCache()", e);
    }

    return success;
}

Thanks for reading. Thanks in advance for your help.

Community
  • 1
  • 1

0 Answers0