| // 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.SystemClock; |
| |
| import org.chromium.base.Log; |
| import org.chromium.base.ThreadUtils; |
| import org.chromium.base.VisibleForTesting; |
| import org.chromium.chrome.browser.profiles.Profile; |
| import org.chromium.net.ConnectionType; |
| import org.chromium.net.NetworkChangeNotifier; |
| |
| import java.util.Collections; |
| import java.util.EnumMap; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import javax.annotation.Nullable; |
| |
| /** |
| * A utility class for checking if the device is currently connected to the Internet by using |
| * both available network stacks, and checking over both HTTP and HTTPS. |
| */ |
| public class ConnectivityTask { |
| private static final String TAG = "feedback"; |
| |
| /** |
| * The key for the data describing how long time from the connection check was started, |
| * until the data was collected. This is to better understand the connection data. |
| * This string is user visible. |
| */ |
| @VisibleForTesting |
| static final String CONNECTION_CHECK_ELAPSED_KEY = "Connection check elapsed (ms)"; |
| |
| /** |
| * The key for the data describing the current connection type. |
| * This string is user visible. |
| */ |
| @VisibleForTesting |
| static final String CONNECTION_TYPE_KEY = "Connection type"; |
| |
| /** |
| * The key for the data describing whether Chrome was able to successfully connect to the |
| * HTTP connection check URL using the Chrome network stack. |
| * This string is user visible. |
| */ |
| @VisibleForTesting |
| static final String CHROME_HTTP_KEY = "HTTP connection check (Chrome network stack)"; |
| |
| /** |
| * The key for the data describing whether Chrome was able to successfully connect to the |
| * HTTPS connection check URL using the Chrome network stack. |
| * This string is user visible. |
| */ |
| @VisibleForTesting |
| static final String CHROME_HTTPS_KEY = "HTTPS connection check (Chrome network stack)"; |
| |
| /** |
| * The key for the data describing whether Chrome was able to successfully connect to the |
| * HTTP connection check URL using the Android network stack. |
| * This string is user visible. |
| */ |
| @VisibleForTesting |
| static final String SYSTEM_HTTP_KEY = "HTTP connection check (Android network stack)"; |
| |
| /** |
| * The key for the data describing whether Chrome was able to successfully connect to the |
| * HTTPS connection check URL using the Android network stack. |
| * This string is user visible. |
| */ |
| @VisibleForTesting |
| static final String SYSTEM_HTTPS_KEY = "HTTPS connection check (Android network stack)"; |
| |
| private static String getHumanReadableType(Type type) { |
| switch (type) { |
| case CHROME_HTTP: |
| return CHROME_HTTP_KEY; |
| case CHROME_HTTPS: |
| return CHROME_HTTPS_KEY; |
| case SYSTEM_HTTP: |
| return SYSTEM_HTTP_KEY; |
| case SYSTEM_HTTPS: |
| return SYSTEM_HTTPS_KEY; |
| default: |
| throw new IllegalArgumentException("Unknown connection type: " + type); |
| } |
| } |
| |
| static String getHumanReadableResult(int result) { |
| switch (result) { |
| case ConnectivityCheckResult.UNKNOWN: |
| return "UNKNOWN"; |
| case ConnectivityCheckResult.CONNECTED: |
| return "CONNECTED"; |
| case ConnectivityCheckResult.NOT_CONNECTED: |
| return "NOT_CONNECTED"; |
| case ConnectivityCheckResult.TIMEOUT: |
| return "TIMEOUT"; |
| case ConnectivityCheckResult.ERROR: |
| return "ERROR"; |
| default: |
| throw new IllegalArgumentException("Unknown result value: " + result); |
| } |
| } |
| |
| static String getHumanReadableConnectionType(int connectionType) { |
| switch (connectionType) { |
| case ConnectionType.CONNECTION_UNKNOWN: |
| return "Unknown"; |
| case ConnectionType.CONNECTION_ETHERNET: |
| return "Ethernet"; |
| case ConnectionType.CONNECTION_WIFI: |
| return "WiFi"; |
| case ConnectionType.CONNECTION_2G: |
| return "2G"; |
| case ConnectionType.CONNECTION_3G: |
| return "3G"; |
| case ConnectionType.CONNECTION_4G: |
| return "4G"; |
| case ConnectionType.CONNECTION_NONE: |
| return "NONE"; |
| case ConnectionType.CONNECTION_BLUETOOTH: |
| return "Bluetooth"; |
| default: |
| return "Unknown connection type " + connectionType; |
| } |
| } |
| |
| /** |
| * ConnectivityResult is the callback for when the result of a connectivity check is ready. |
| */ |
| interface ConnectivityResult { |
| /** |
| * Called when the FeedbackData is ready. |
| */ |
| void onResult(FeedbackData feedbackData); |
| } |
| |
| /** |
| * FeedbackData contains the set of information that is to be included in a feedback report. |
| */ |
| static final class FeedbackData { |
| private final Map<Type, Integer> mConnections; |
| private final int mTimeoutMs; |
| private final long mElapsedTimeMs; |
| private final int mConnectionType; |
| |
| FeedbackData(Map<Type, Integer> connections, int timeoutMs, long elapsedTimeMs, |
| int connectionType) { |
| mConnections = connections; |
| mTimeoutMs = timeoutMs; |
| mElapsedTimeMs = elapsedTimeMs; |
| mConnectionType = connectionType; |
| } |
| |
| /** |
| * @return a {@link Map} with information about connection status for different connection |
| * types. |
| */ |
| @VisibleForTesting |
| Map<Type, Integer> getConnections() { |
| return Collections.unmodifiableMap(mConnections); |
| } |
| |
| /** |
| * @return the timeout that was used for data collection. |
| */ |
| @VisibleForTesting |
| int getTimeoutMs() { |
| return mTimeoutMs; |
| } |
| |
| /** |
| * @return the time that was used from starting the check until data was gathered. |
| */ |
| @VisibleForTesting |
| long getElapsedTimeMs() { |
| return mElapsedTimeMs; |
| } |
| |
| /** |
| * @return a {@link Map} with all the data fields for this feedback. |
| */ |
| Map<String, String> toMap() { |
| Map<String, String> map = new HashMap<>(); |
| for (Map.Entry<Type, Integer> entry : mConnections.entrySet()) { |
| map.put(getHumanReadableType(entry.getKey()), |
| getHumanReadableResult(entry.getValue())); |
| } |
| map.put(CONNECTION_CHECK_ELAPSED_KEY, String.valueOf(mElapsedTimeMs)); |
| map.put(CONNECTION_TYPE_KEY, getHumanReadableConnectionType(mConnectionType)); |
| return map; |
| } |
| } |
| |
| /** |
| * The type of network stack and connectivity check this result is about. |
| */ |
| public enum Type { CHROME_HTTP, CHROME_HTTPS, SYSTEM_HTTP, SYSTEM_HTTPS } |
| |
| private class SingleTypeTask implements ConnectivityChecker.ConnectivityCheckerCallback { |
| private final Type mType; |
| |
| public SingleTypeTask(Type type) { |
| mType = type; |
| } |
| |
| /** |
| * Starts the current task by calling the appropriate method on the |
| * {@link ConnectivityChecker}. |
| * The result will be put in {@link #mResult} when it comes back from the network stack. |
| */ |
| public void start(Profile profile, int timeoutMs) { |
| Log.v(TAG, "Starting task for " + mType); |
| switch (mType) { |
| case CHROME_HTTP: |
| ConnectivityChecker.checkConnectivityChromeNetworkStack( |
| profile, false, timeoutMs, this); |
| break; |
| case CHROME_HTTPS: |
| ConnectivityChecker.checkConnectivityChromeNetworkStack( |
| profile, true, timeoutMs, this); |
| break; |
| case SYSTEM_HTTP: |
| ConnectivityChecker.checkConnectivitySystemNetworkStack(false, timeoutMs, this); |
| break; |
| case SYSTEM_HTTPS: |
| ConnectivityChecker.checkConnectivitySystemNetworkStack(true, timeoutMs, this); |
| break; |
| default: |
| Log.e(TAG, "Failed to recognize type " + mType); |
| } |
| } |
| |
| @Override |
| public void onResult(int result) { |
| ThreadUtils.assertOnUiThread(); |
| Log.v(TAG, "Got result for " + getHumanReadableType(mType) + ": result = " |
| + getHumanReadableResult(result)); |
| mResult.put(mType, result); |
| if (isDone()) postCallbackResult(); |
| } |
| |
| private void postCallbackResult() { |
| if (mCallback == null) return; |
| ThreadUtils.postOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| mCallback.onResult(get()); |
| } |
| }); |
| } |
| } |
| |
| private final Map<Type, Integer> mResult = new EnumMap<Type, Integer>(Type.class); |
| private final int mTimeoutMs; |
| private final ConnectivityResult mCallback; |
| private final long mStartCheckTimeMs; |
| |
| @VisibleForTesting |
| ConnectivityTask(Profile profile, int timeoutMs, ConnectivityResult callback) { |
| mTimeoutMs = timeoutMs; |
| mCallback = callback; |
| mStartCheckTimeMs = SystemClock.elapsedRealtime(); |
| init(profile, timeoutMs); |
| } |
| |
| @VisibleForTesting |
| void init(Profile profile, int timeoutMs) { |
| for (Type t : Type.values()) { |
| SingleTypeTask task = new SingleTypeTask(t); |
| task.start(profile, timeoutMs); |
| } |
| } |
| |
| /** |
| * @return whether all connectivity type results have been collected. |
| */ |
| public boolean isDone() { |
| ThreadUtils.assertOnUiThread(); |
| return mResult.size() == Type.values().length; |
| } |
| |
| /** |
| * Retrieves the connectivity that has been collected up until this call. This method fills in |
| * {@link ConnectivityCheckResult#UNKNOWN} for results that have not been retrieved yet. |
| * |
| * @return the {@link FeedbackData}. |
| */ |
| public FeedbackData get() { |
| ThreadUtils.assertOnUiThread(); |
| Map<Type, Integer> result = new EnumMap<Type, Integer>(Type.class); |
| // Ensure the map is filled with a result for all {@link Type}s. |
| for (Type type : Type.values()) { |
| if (mResult.containsKey(type)) { |
| result.put(type, mResult.get(type)); |
| } else { |
| result.put(type, ConnectivityCheckResult.UNKNOWN); |
| } |
| } |
| long elapsedTimeMs = SystemClock.elapsedRealtime() - mStartCheckTimeMs; |
| int connectionType = NetworkChangeNotifier.getInstance().getCurrentConnectionType(); |
| return new FeedbackData(result, mTimeoutMs, elapsedTimeMs, connectionType); |
| } |
| |
| /** |
| * Starts an asynchronous request for checking whether the device is currently connected to the |
| * Internet using both the Chrome and the Android system network stack. |
| * |
| * The result will be given back in the {@link ConnectivityResult} callback that is passed in, |
| * either when all results have been gathered successfully or if a timeout happened. The result |
| * can also be retrieved by calling {@link #get}, and this call must happen from the main |
| * thread. {@link #isDone} can be used to see if all requests have been completed. It is OK to |
| * get the result before {@link #isDone()} returns true. |
| * |
| * @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 for the result. May be null. |
| * @return a ConnectivityTask to retrieve the results. |
| */ |
| public static ConnectivityTask create( |
| Profile profile, int timeoutMs, @Nullable ConnectivityResult callback) { |
| ThreadUtils.assertOnUiThread(); |
| return new ConnectivityTask(profile, timeoutMs, callback); |
| } |
| } |