// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.feedback;
import android.os.AsyncTask;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.chrome.browser.profiles.Profile;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.SocketTimeoutException;
import java.net.URL;
/**
* A utility class for checking if the device is currently connected to the Internet.
*/
@JNINamespace("chrome::android")
public final class ConnectivityChecker {
private static final String TAG = "feedback";
private static final String DEFAULT_HTTP_NO_CONTENT_URL =
"http://clients4.google.com/generate_204";
private static final String DEFAULT_HTTPS_NO_CONTENT_URL =
"https://clients4.google.com/generate_204";
private static String sHttpNoContentUrl = DEFAULT_HTTP_NO_CONTENT_URL;
private static String sHttpsNoContentUrl = DEFAULT_HTTPS_NO_CONTENT_URL;
/**
* A callback for whether the device is currently connected to the Internet.
*/
public interface ConnectivityCheckerCallback {
/**
* Called when the result of the connectivity check is ready.
*/
void onResult(int result);
}
@VisibleForTesting
static void overrideUrlsForTest(String httpUrl, String httpsUrl) {
ThreadUtils.assertOnUiThread();
sHttpNoContentUrl = httpUrl;
sHttpsNoContentUrl = httpsUrl;
}
private static void postResult(final ConnectivityCheckerCallback callback, final int result) {
ThreadUtils.postOnUiThread(new Runnable() {
@Override
public void run() {
callback.onResult(result);
}
});
}
/**
* Starts an asynchronous request for checking whether the device is currently connected to the
* Internet using the Android system network stack. The result passed to the callback denotes
* whether the attempt to connect to the server was successful.
*
* If the profile or URL is invalid, the callback will be called with false.
* The server reply for the URL must respond with HTTP 204 without any redirects for the
* connectivity check to be successful.
*
* This method takes ownership of the callback object until the callback has happened.
* This method must be called on the main thread.
* @param timeoutMs number of milliseconds to wait before giving up waiting for a connection.
* @param callback the callback which will get the result.
*/
public static void checkConnectivitySystemNetworkStack(
boolean useHttps, int timeoutMs, ConnectivityCheckerCallback callback) {
String url = useHttps ? sHttpsNoContentUrl : sHttpNoContentUrl;
checkConnectivitySystemNetworkStack(url, timeoutMs, callback);
}
static void checkConnectivitySystemNetworkStack(
String urlStr, final int timeoutMs, final ConnectivityCheckerCallback callback) {
if (!nativeIsUrlValid(urlStr)) {
Log.w(TAG, "Predefined URL invalid.");
postResult(callback, ConnectivityCheckResult.ERROR);
return;
}
final URL url;
try {
url = new URL(urlStr);
} catch (MalformedURLException e) {
Log.w(TAG, "Failed to parse predefined URL: " + e);
postResult(callback, ConnectivityCheckResult.ERROR);
return;
}
new AsyncTask<String, Void, Integer>() {
@Override
protected Integer doInBackground(String... strings) {
try {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setInstanceFollowRedirects(false);
conn.setRequestMethod("GET");
conn.setDoInput(false);
conn.setDoOutput(false);
conn.setConnectTimeout(timeoutMs);
conn.setReadTimeout(timeoutMs);
conn.connect();
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_NO_CONTENT) {
return ConnectivityCheckResult.CONNECTED;
} else {
return ConnectivityCheckResult.NOT_CONNECTED;
}
} catch (SocketTimeoutException e) {
return ConnectivityCheckResult.TIMEOUT;
} catch (ProtocolException e) {
return ConnectivityCheckResult.ERROR;
} catch (IOException e) {
return ConnectivityCheckResult.NOT_CONNECTED;
}
}
@Override
protected void onPostExecute(Integer result) {
callback.onResult(result);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
/**
* Starts an asynchronous request for checking whether the device is currently connected to the
* Internet using the Chrome network stack. The result passed to the callback denotes whether
*the
* attempt to connect to the server was successful.
*
* If the profile or URL is invalid, the callback will be called with false.
* The server reply for the URL must respond with HTTP 204 without any redirects for the
* connectivity check to be successful.
*
* This method takes ownership of the callback object until the callback has happened.
* This method must be called on the main thread.
* @param profile the context to do the check in.
* @param timeoutMs number of milliseconds to wait before giving up waiting for a connection.
* @param callback the callback which will get the result.
*/
public static void checkConnectivityChromeNetworkStack(Profile profile, boolean useHttps,
int timeoutMs, ConnectivityCheckerCallback callback) {
String url = useHttps ? sHttpsNoContentUrl : sHttpNoContentUrl;
checkConnectivityChromeNetworkStack(profile, url, timeoutMs, callback);
}
@VisibleForTesting
static void checkConnectivityChromeNetworkStack(
Profile profile, String url, long timeoutMs, ConnectivityCheckerCallback callback) {
ThreadUtils.assertOnUiThread();
nativeCheckConnectivity(profile, url, timeoutMs, callback);
}
@CalledByNative
private static void executeCallback(Object callback, int result) {
((ConnectivityCheckerCallback) callback).onResult(result);
}
private ConnectivityChecker() {}
private static native void nativeCheckConnectivity(
Profile profile, String url, long timeoutMs, ConnectivityCheckerCallback callback);
private static native boolean nativeIsUrlValid(String url);
}