1ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen/* 2ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * Copyright (C) 2014 The Android Open Source Project 3ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * 4ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * Licensed under the Apache License, Version 2.0 (the "License"); 5ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * you may not use this file except in compliance with the License. 6ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * You may obtain a copy of the License at 7ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * 8ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * http://www.apache.org/licenses/LICENSE-2.0 9ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * 10ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * Unless required by applicable law or agreed to in writing, software 11ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * distributed under the License is distributed on an "AS IS" BASIS, 12ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * See the License for the specific language governing permissions and 14ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * limitations under the License. 15ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen */ 16ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 17ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenpackage com.android.server.connectivity; 18ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 1949e3edff5156f471819e4ea2a88994bca70bd870Paul Jensenimport static android.net.CaptivePortal.APP_RETURN_DISMISSED; 2049e3edff5156f471819e4ea2a88994bca70bd870Paul Jensenimport static android.net.CaptivePortal.APP_RETURN_UNWANTED; 2149e3edff5156f471819e4ea2a88994bca70bd870Paul Jensenimport static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS; 2249e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen 2379a08051c5588d8420656813b21993d490e93dd0Paul Jensenimport android.app.AlarmManager; 24869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.app.PendingIntent; 25869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.content.BroadcastReceiver; 26ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport android.content.Context; 27869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.content.Intent; 28869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.content.IntentFilter; 2949e3edff5156f471819e4ea2a88994bca70bd870Paul Jensenimport android.net.CaptivePortal; 30869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.net.ConnectivityManager; 3149e3edff5156f471819e4ea2a88994bca70bd870Paul Jensenimport android.net.ICaptivePortal; 322c311d61eaf331818e601f97485f88c4cf26384dPaul Jensenimport android.net.NetworkRequest; 338fe1742946e92dfe7e01e6a06f48ad626c01bd35Paul Jensenimport android.net.ProxyInfo; 347ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensenimport android.net.TrafficStats; 3571b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensenimport android.net.Uri; 36cfddd6879283860bb4d2cf2972ea086f585a37ecHugo Benichiimport android.net.metrics.IpConnectivityLog; 37cc92c6e87773df9d5a84922066716ae9bb09cd6dHugo Benichiimport android.net.metrics.NetworkEvent; 38cfddd6879283860bb4d2cf2972ea086f585a37ecHugo Benichiimport android.net.metrics.ValidationProbeEvent; 39d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichiimport android.net.util.Stopwatch; 40306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.net.wifi.WifiInfo; 41306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.net.wifi.WifiManager; 42ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport android.os.Handler; 43ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport android.os.Message; 44306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.os.SystemClock; 45869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.os.UserHandle; 46ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport android.provider.Settings; 47306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.telephony.CellIdentityCdma; 48306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.telephony.CellIdentityGsm; 49306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.telephony.CellIdentityLte; 50306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.telephony.CellIdentityWcdma; 51306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.telephony.CellInfo; 52306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.telephony.CellInfoCdma; 53306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.telephony.CellInfoGsm; 54306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.telephony.CellInfoLte; 55306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.telephony.CellInfoWcdma; 56306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.telephony.TelephonyManager; 572f0a8974d1f45aad590829042df8cff07e989635Paul Jensenimport android.text.TextUtils; 5822b4c6a027d72ec90dc91d150bee007cb8167eedRobert Greenwaltimport android.util.LocalLog; 5922b4c6a027d72ec90dc91d150bee007cb8167eedRobert Greenwaltimport android.util.LocalLog.ReadOnlyLocalLog; 60532b6143f6369224b0c220bad08ae48a979b5adfPaul Jensenimport android.util.Log; 61ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 62d7b6ca91e9ecac15949a4484d560cfab5833a431Paul Jensenimport com.android.internal.annotations.VisibleForTesting; 63ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport com.android.internal.util.Protocol; 64ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport com.android.internal.util.State; 65ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport com.android.internal.util.StateMachine; 66ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 67ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport java.io.IOException; 68ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport java.net.HttpURLConnection; 692f0a8974d1f45aad590829042df8cff07e989635Paul Jensenimport java.net.InetAddress; 70c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colittiimport java.net.MalformedURLException; 71ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport java.net.URL; 72d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichiimport java.net.UnknownHostException; 73306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport java.util.List; 7471b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensenimport java.util.Random; 75d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichiimport java.util.concurrent.CountDownLatch; 76d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichiimport java.util.concurrent.TimeUnit; 77ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 78ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen/** 79ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * {@hide} 80ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen */ 81ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenpublic class NetworkMonitor extends StateMachine { 82a488c23dd5c9e024fb8ec702cee722916cdeaf0eErik Kline private static final String TAG = NetworkMonitor.class.getSimpleName(); 83a4f17bcbdf787852267a6f24d83ca2011f0decacHugo Benichi private static final boolean DBG = true; 84a4f17bcbdf787852267a6f24d83ca2011f0decacHugo Benichi private static final boolean VDBG = false; 8592eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi 8611ae28f387bc499ff82448d978dac9524b03f670Hugo Benichi // Default configuration values for captive portal detection probes. 8711ae28f387bc499ff82448d978dac9524b03f670Hugo Benichi // TODO: append a random length parameter to the default HTTPS url. 8811ae28f387bc499ff82448d978dac9524b03f670Hugo Benichi // TODO: randomize browser version ids in the default User-Agent String. 8911ae28f387bc499ff82448d978dac9524b03f670Hugo Benichi private static final String DEFAULT_HTTPS_URL = "https://www.google.com/generate_204"; 9092eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi private static final String DEFAULT_HTTP_URL = 9192eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi "http://connectivitycheck.gstatic.com/generate_204"; 9211ae28f387bc499ff82448d978dac9524b03f670Hugo Benichi private static final String DEFAULT_FALLBACK_URL = "http://www.google.com/gen_204"; 9311ae28f387bc499ff82448d978dac9524b03f670Hugo Benichi private static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) " 9411ae28f387bc499ff82448d978dac9524b03f670Hugo Benichi + "AppleWebKit/537.36 (KHTML, like Gecko) " 9511ae28f387bc499ff82448d978dac9524b03f670Hugo Benichi + "Chrome/52.0.2743.82 Safari/537.36"; 9692eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi 97ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen private static final int SOCKET_TIMEOUT_MS = 10000; 9892eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi private static final int PROBE_TIMEOUT_MS = 3000; 9992eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi 100dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi static enum EvaluationResult { 101dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi VALIDATED(true), 102dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi CAPTIVE_PORTAL(false); 103dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi final boolean isValidated; 104dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi EvaluationResult(boolean isValidated) { 105dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi this.isValidated = isValidated; 106dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi } 107dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi } 108dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi 109dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi static enum ValidationStage { 110dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi FIRST_VALIDATION(true), 111dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi REVALIDATION(false); 112dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi final boolean isFirstValidation; 113dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi ValidationStage(boolean isFirstValidation) { 114dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi this.isFirstValidation = isFirstValidation; 115dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi } 116dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi } 117dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi 118306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen public static final String ACTION_NETWORK_CONDITIONS_MEASURED = 119306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen "android.net.conn.NETWORK_CONDITIONS_MEASURED"; 120306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type"; 121306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen public static final String EXTRA_NETWORK_TYPE = "extra_network_type"; 122306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received"; 123306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal"; 124306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen public static final String EXTRA_CELL_ID = "extra_cellid"; 125306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen public static final String EXTRA_SSID = "extra_ssid"; 126306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen public static final String EXTRA_BSSID = "extra_bssid"; 127306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen /** real time since boot */ 128306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms"; 129306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms"; 130306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen 131306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen private static final String PERMISSION_ACCESS_NETWORK_CONDITIONS = 132306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen "android.permission.ACCESS_NETWORK_CONDITIONS"; 133ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 134ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED. 135ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen // The network should be used as a default internet connection. It was found to be: 136ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen // 1. a functioning network providing internet access, or 137ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen // 2. a captive portal and the user decided to use it as is. 138ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen public static final int NETWORK_TEST_RESULT_VALID = 0; 139ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED. 140ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen // The network should not be used as a default internet connection. It was found to be: 141ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen // 1. a captive portal and the user is prompted to sign-in, or 142ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen // 2. a captive portal and the user did not want to use it, or 143ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen // 3. a broken network (e.g. DNS failed, connect failed, HTTP request failed). 144ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen public static final int NETWORK_TEST_RESULT_INVALID = 1; 145ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen 146ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen private static final int BASE = Protocol.BASE_NETWORK_MONITOR; 147ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 148ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen /** 149ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * Inform NetworkMonitor that their network is connected. 150ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * Initiates Network Validation. 151ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen */ 152ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen public static final int CMD_NETWORK_CONNECTED = BASE + 1; 153ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 154ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen /** 155ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen * Inform ConnectivityService that the network has been tested. 1562324373124f0ba4e59ba6d3de9e274f2fa28cff0Paul Jensen * obj = String representing URL that Internet probe was redirect to, if it was redirected. 157ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen * arg1 = One of the NETWORK_TESTED_RESULT_* constants. 1582324373124f0ba4e59ba6d3de9e274f2fa28cff0Paul Jensen * arg2 = NetID. 159ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen */ 160ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen public static final int EVENT_NETWORK_TESTED = BASE + 2; 161ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 162ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen /** 163ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * Message to self indicating it's time to evaluate a network's connectivity. 164ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * arg1 = Token to ignore old messages. 165ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen */ 166869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen private static final int CMD_REEVALUATE = BASE + 6; 167ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 168ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen /** 169ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * Inform NetworkMonitor that the network has disconnected. 170ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen */ 1717ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen public static final int CMD_NETWORK_DISCONNECTED = BASE + 7; 172ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 173ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen /** 174ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * Force evaluation even if it has succeeded in the past. 1757ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen * arg1 = UID responsible for requesting this reeval. Will be billed for data. 176ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen */ 1777ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen public static final int CMD_FORCE_REEVALUATION = BASE + 8; 178869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen 179869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen /** 18071b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen * Message to self indicating captive portal app finished. 18149e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen * arg1 = one of: APP_RETURN_DISMISSED, 18249e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen * APP_RETURN_UNWANTED, 18349e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen * APP_RETURN_WANTED_AS_IS 18425a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen * obj = mCaptivePortalLoggedInResponseToken as String 185869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen */ 18649e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = BASE + 9; 187869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen 188869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen /** 189869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen * Request ConnectivityService display provisioning notification. 190869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen * arg1 = Whether to make the notification visible. 191fdc4e4af7305514359e041dbec742f4b6561e393Paul Jensen * arg2 = NetID. 192fdc4e4af7305514359e041dbec742f4b6561e393Paul Jensen * obj = Intent to be launched when notification selected by user, null if !arg1. 193869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen */ 19471b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 10; 195869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen 196869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen /** 19725a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen * Message to self indicating sign-in app should be launched. 19825a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen * Sent by mLaunchCaptivePortalAppBroadcastReceiver when the 19925a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen * user touches the sign in notification. 200869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen */ 20125a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11; 202ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 203ee3e2ce4eea8a1788d91001224ef9f231c399b95Paul Jensen /** 204ee3e2ce4eea8a1788d91001224ef9f231c399b95Paul Jensen * Retest network to see if captive portal is still in place. 205ee3e2ce4eea8a1788d91001224ef9f231c399b95Paul Jensen * arg1 = UID responsible for requesting this reeval. Will be billed for data. 206ee3e2ce4eea8a1788d91001224ef9f231c399b95Paul Jensen * 0 indicates self-initiated, so nobody to blame. 207ee3e2ce4eea8a1788d91001224ef9f231c399b95Paul Jensen */ 208ee3e2ce4eea8a1788d91001224ef9f231c399b95Paul Jensen private static final int CMD_CAPTIVE_PORTAL_RECHECK = BASE + 12; 209ee3e2ce4eea8a1788d91001224ef9f231c399b95Paul Jensen 210d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen // Start mReevaluateDelayMs at this value and double. 211d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen private static final int INITIAL_REEVALUATE_DELAY_MS = 1000; 212d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen private static final int MAX_REEVALUATE_DELAY_MS = 10*60*1000; 213d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen // Before network has been evaluated this many times, ignore repeated reevaluate requests. 214d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen private static final int IGNORE_REEVALUATE_ATTEMPTS = 5; 215ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen private int mReevaluateToken = 0; 2167ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen private static final int INVALID_UID = -1; 2177ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen private int mUidResponsibleForReeval = INVALID_UID; 218d9be23fa4cafc41dcb8b5e2f090e3f2d91197bfbPaul Jensen // Stop blaming UID that requested re-evaluation after this many attempts. 219d9be23fa4cafc41dcb8b5e2f090e3f2d91197bfbPaul Jensen private static final int BLAME_FOR_EVALUATION_ATTEMPTS = 5; 220ee3e2ce4eea8a1788d91001224ef9f231c399b95Paul Jensen // Delay between reevaluations once a captive portal has been found. 221ee3e2ce4eea8a1788d91001224ef9f231c399b95Paul Jensen private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10*60*1000; 222ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 223ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen private final Context mContext; 224ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen private final Handler mConnectivityServiceHandler; 225ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen private final NetworkAgentInfo mNetworkAgentInfo; 226a488c23dd5c9e024fb8ec702cee722916cdeaf0eErik Kline private final int mNetId; 227306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen private final TelephonyManager mTelephonyManager; 228306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen private final WifiManager mWifiManager; 22979a08051c5588d8420656813b21993d490e93dd0Paul Jensen private final AlarmManager mAlarmManager; 2302c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen private final NetworkRequest mDefaultRequest; 231f9fdf87af2fe0cb9b70ffa81a0912448f4ef4207Hugo Benichi private final IpConnectivityLog mMetricsLog; 232ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 2334bc78eba6f16ef84206aaed9edd5ca4bb4f6c420Calvin On @VisibleForTesting 2344bc78eba6f16ef84206aaed9edd5ca4bb4f6c420Calvin On protected boolean mIsCaptivePortalCheckEnabled; 2354bc78eba6f16ef84206aaed9edd5ca4bb4f6c420Calvin On 236c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti private boolean mUseHttps; 237dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi // The total number of captive portal detection attempts for this NetworkMonitor instance. 238dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi private int mValidations = 0; 239ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 240ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app. 241ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen private boolean mUserDoesNotWant = false; 242700f236afb373125353a304d9098557890b9253fPaul Jensen // Avoids surfacing "Sign in to network" notification. 243700f236afb373125353a304d9098557890b9253fPaul Jensen private boolean mDontDisplaySigninNotification = false; 244ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen 245fb68f8fbe0213841f393f8bdb5313e4e44f4f116Robert Greenwalt public boolean systemReady = false; 246fb68f8fbe0213841f393f8bdb5313e4e44f4f116Robert Greenwalt 24771b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen private final State mDefaultState = new DefaultState(); 24871b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen private final State mValidatedState = new ValidatedState(); 24971b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen private final State mMaybeNotifyState = new MaybeNotifyState(); 25071b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen private final State mEvaluatingState = new EvaluatingState(); 25171b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen private final State mCaptivePortalState = new CaptivePortalState(); 25271b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen 25325a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null; 254ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 25522b4c6a027d72ec90dc91d150bee007cb8167eedRobert Greenwalt private final LocalLog validationLogs = new LocalLog(20); // 20 lines 25622b4c6a027d72ec90dc91d150bee007cb8167eedRobert Greenwalt 257a488c23dd5c9e024fb8ec702cee722916cdeaf0eErik Kline private final Stopwatch mEvaluationTimer = new Stopwatch(); 258a488c23dd5c9e024fb8ec702cee722916cdeaf0eErik Kline 259d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi // This variable is set before transitioning to the mCaptivePortalState. 260d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi private CaptivePortalProbeResult mLastPortalProbeResult = CaptivePortalProbeResult.FAILED; 261d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi 2622c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo, 2632c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen NetworkRequest defaultRequest) { 264f9fdf87af2fe0cb9b70ffa81a0912448f4ef4207Hugo Benichi this(context, handler, networkAgentInfo, defaultRequest, new IpConnectivityLog()); 265f9fdf87af2fe0cb9b70ffa81a0912448f4ef4207Hugo Benichi } 266f9fdf87af2fe0cb9b70ffa81a0912448f4ef4207Hugo Benichi 267f9fdf87af2fe0cb9b70ffa81a0912448f4ef4207Hugo Benichi @VisibleForTesting 268f9fdf87af2fe0cb9b70ffa81a0912448f4ef4207Hugo Benichi protected NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo, 269f9fdf87af2fe0cb9b70ffa81a0912448f4ef4207Hugo Benichi NetworkRequest defaultRequest, IpConnectivityLog logger) { 270ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen // Add suffix indicating which NetworkMonitor we're talking about. 271ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen super(TAG + networkAgentInfo.name()); 272ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 273ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen mContext = context; 274f9fdf87af2fe0cb9b70ffa81a0912448f4ef4207Hugo Benichi mMetricsLog = logger; 275ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen mConnectivityServiceHandler = handler; 276ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen mNetworkAgentInfo = networkAgentInfo; 277a488c23dd5c9e024fb8ec702cee722916cdeaf0eErik Kline mNetId = mNetworkAgentInfo.network.netId; 278306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 279306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 28079a08051c5588d8420656813b21993d490e93dd0Paul Jensen mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 2812c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen mDefaultRequest = defaultRequest; 282ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 283ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen addState(mDefaultState); 284ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen addState(mValidatedState, mDefaultState); 28571b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen addState(mMaybeNotifyState, mDefaultState); 28671b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen addState(mEvaluatingState, mMaybeNotifyState); 28771b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen addState(mCaptivePortalState, mMaybeNotifyState); 28849f63fbed4cd84f5da182c85e8b999037dc64f3bRobert Greenwalt setInitialState(mDefaultState); 289ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 290869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(), 2914bc78eba6f16ef84206aaed9edd5ca4bb4f6c420Calvin On Settings.Global.CAPTIVE_PORTAL_MODE, Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT) 2924bc78eba6f16ef84206aaed9edd5ca4bb4f6c420Calvin On != Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE; 293c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti mUseHttps = Settings.Global.getInt(mContext.getContentResolver(), 294c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1; 295ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 296ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen start(); 297ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } 298ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 299532b6143f6369224b0c220bad08ae48a979b5adfPaul Jensen @Override 300532b6143f6369224b0c220bad08ae48a979b5adfPaul Jensen protected void log(String s) { 30122e547ab74eb169782ddb8dc1a16dc327b7cd328Paul Jensen if (DBG) Log.d(TAG + "/" + mNetworkAgentInfo.name(), s); 302532b6143f6369224b0c220bad08ae48a979b5adfPaul Jensen } 303532b6143f6369224b0c220bad08ae48a979b5adfPaul Jensen 30422b4c6a027d72ec90dc91d150bee007cb8167eedRobert Greenwalt private void validationLog(String s) { 30522b4c6a027d72ec90dc91d150bee007cb8167eedRobert Greenwalt if (DBG) log(s); 30622b4c6a027d72ec90dc91d150bee007cb8167eedRobert Greenwalt validationLogs.log(s); 30722b4c6a027d72ec90dc91d150bee007cb8167eedRobert Greenwalt } 30822b4c6a027d72ec90dc91d150bee007cb8167eedRobert Greenwalt 30922b4c6a027d72ec90dc91d150bee007cb8167eedRobert Greenwalt public ReadOnlyLocalLog getValidationLogs() { 31022b4c6a027d72ec90dc91d150bee007cb8167eedRobert Greenwalt return validationLogs.readOnlyLocalLog(); 31122b4c6a027d72ec90dc91d150bee007cb8167eedRobert Greenwalt } 31222b4c6a027d72ec90dc91d150bee007cb8167eedRobert Greenwalt 313dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi private ValidationStage validationStage() { 314dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi return 0 == mValidations ? ValidationStage.FIRST_VALIDATION : ValidationStage.REVALIDATION; 315dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi } 316dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi 31771b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen // DefaultState is the parent of all States. It exists only to handle CMD_* messages but 31871b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen // does not entail any real state (hence no enter() or exit() routines). 319ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen private class DefaultState extends State { 320ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen @Override 321ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen public boolean processMessage(Message message) { 322ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen switch (message.what) { 323ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen case CMD_NETWORK_CONNECTED: 324cfddd6879283860bb4d2cf2972ea086f585a37ecHugo Benichi logNetworkEvent(NetworkEvent.NETWORK_CONNECTED); 325ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen transitionTo(mEvaluatingState); 326d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen return HANDLED; 327ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen case CMD_NETWORK_DISCONNECTED: 328cfddd6879283860bb4d2cf2972ea086f585a37ecHugo Benichi logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED); 32925a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen if (mLaunchCaptivePortalAppBroadcastReceiver != null) { 33025a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen mContext.unregisterReceiver(mLaunchCaptivePortalAppBroadcastReceiver); 33125a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen mLaunchCaptivePortalAppBroadcastReceiver = null; 33271b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen } 3331fd9aeef08fac363ec3ef2eb61cea519a04c51fdRobert Greenwalt quit(); 334d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen return HANDLED; 335ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen case CMD_FORCE_REEVALUATION: 336ee3e2ce4eea8a1788d91001224ef9f231c399b95Paul Jensen case CMD_CAPTIVE_PORTAL_RECHECK: 33722e547ab74eb169782ddb8dc1a16dc327b7cd328Paul Jensen log("Forcing reevaluation for UID " + message.arg1); 3387ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen mUidResponsibleForReeval = message.arg1; 339ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen transitionTo(mEvaluatingState); 340d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen return HANDLED; 34171b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen case CMD_CAPTIVE_PORTAL_APP_FINISHED: 34222e547ab74eb169782ddb8dc1a16dc327b7cd328Paul Jensen log("CaptivePortal App responded with " + message.arg1); 343c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 344c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // If the user has seen and acted on a captive portal notification, and the 345c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // captive portal app is now closed, disable HTTPS probes. This avoids the 346c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // following pathological situation: 347c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // 348c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // 1. HTTP probe returns a captive portal, HTTPS probe fails or times out. 349c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // 2. User opens the app and logs into the captive portal. 350c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // 3. HTTP starts working, but HTTPS still doesn't work for some other reason - 351c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // perhaps due to the network blocking HTTPS? 352c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // 353c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // In this case, we'll fail to validate the network even after the app is 354c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // dismissed. There is now no way to use this network, because the app is now 355c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // gone, so the user cannot select "Use this network as is". 356c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti mUseHttps = false; 357c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 35871b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen switch (message.arg1) { 35949e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen case APP_RETURN_DISMISSED: 360d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen sendMessage(CMD_FORCE_REEVALUATION, 0 /* no UID */, 0); 36125a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen break; 36249e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen case APP_RETURN_WANTED_AS_IS: 363700f236afb373125353a304d9098557890b9253fPaul Jensen mDontDisplaySigninNotification = true; 36425a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen // TODO: Distinguish this from a network that actually validates. 36525a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen // Displaying the "!" on the system UI icon may still be a good idea. 36671b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen transitionTo(mValidatedState); 36771b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen break; 36849e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen case APP_RETURN_UNWANTED: 369700f236afb373125353a304d9098557890b9253fPaul Jensen mDontDisplaySigninNotification = true; 37071b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen mUserDoesNotWant = true; 371d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen mConnectivityServiceHandler.sendMessage(obtainMessage( 3722324373124f0ba4e59ba6d3de9e274f2fa28cff0Paul Jensen EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, 373a488c23dd5c9e024fb8ec702cee722916cdeaf0eErik Kline mNetId, null)); 37471b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen // TODO: Should teardown network. 375d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen mUidResponsibleForReeval = 0; 376d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen transitionTo(mEvaluatingState); 37771b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen break; 37871b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen } 37971b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen return HANDLED; 380ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen default: 381d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen return HANDLED; 382ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } 383ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } 384ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } 385ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 38671b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen // Being in the ValidatedState State indicates a Network is: 38771b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen // - Successfully validated, or 38871b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen // - Wanted "as is" by the user, or 389cf4c2c637268b1a2979e20a8b5644916777a02a4Paul Jensen // - Does not satisfy the default NetworkRequest and so validation has been skipped. 390ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen private class ValidatedState extends State { 391ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen @Override 392ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen public void enter() { 393dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi maybeLogEvaluationResult( 394dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi networkEventType(validationStage(), EvaluationResult.VALIDATED)); 395ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED, 3962324373124f0ba4e59ba6d3de9e274f2fa28cff0Paul Jensen NETWORK_TEST_RESULT_VALID, mNetworkAgentInfo.network.netId, null)); 397dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi mValidations++; 398ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } 399ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 400ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen @Override 401ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen public boolean processMessage(Message message) { 402ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen switch (message.what) { 403ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen case CMD_NETWORK_CONNECTED: 404ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen transitionTo(mValidatedState); 405d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen return HANDLED; 406ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen default: 407ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen return NOT_HANDLED; 408ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } 409ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } 410ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } 411ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 41271b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen // Being in the MaybeNotifyState State indicates the user may have been notified that sign-in 41371b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen // is required. This State takes care to clear the notification upon exit from the State. 41471b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen private class MaybeNotifyState extends State { 41571b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen @Override 41625a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen public boolean processMessage(Message message) { 41725a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen switch (message.what) { 41825a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen case CMD_LAUNCH_CAPTIVE_PORTAL_APP: 41925a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen final Intent intent = new Intent( 42025a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN); 42125a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen intent.putExtra(ConnectivityManager.EXTRA_NETWORK, mNetworkAgentInfo.network); 42249e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL, 42349e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen new CaptivePortal(new ICaptivePortal.Stub() { 42449e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen @Override 42549e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen public void appResponse(int response) { 42649e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen if (response == APP_RETURN_WANTED_AS_IS) { 42749e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen mContext.enforceCallingPermission( 42849e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen android.Manifest.permission.CONNECTIVITY_INTERNAL, 42949e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen "CaptivePortal"); 43049e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen } 43149e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen sendMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, response); 43249e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen } 43349e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen })); 434d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL, 435d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi mLastPortalProbeResult.detectUrl); 436cdf3ba48ccef0f9c6ca8724c1c106df0dd725ad0Hugo Benichi intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT, 437cdf3ba48ccef0f9c6ca8724c1c106df0dd725ad0Hugo Benichi getCaptivePortalUserAgent(mContext)); 43825a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen intent.setFlags( 43925a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); 44025a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen mContext.startActivityAsUser(intent, UserHandle.CURRENT); 44125a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen return HANDLED; 44225a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen default: 44325a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen return NOT_HANDLED; 44425a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen } 44525a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen } 44625a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen 44725a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen @Override 44871b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen public void exit() { 44971b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0, 45071b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen mNetworkAgentInfo.network.netId, null); 45171b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen mConnectivityServiceHandler.sendMessage(message); 45271b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen } 45371b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen } 45471b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen 4552324373124f0ba4e59ba6d3de9e274f2fa28cff0Paul Jensen /** 4562324373124f0ba4e59ba6d3de9e274f2fa28cff0Paul Jensen * Result of calling isCaptivePortal(). 4572324373124f0ba4e59ba6d3de9e274f2fa28cff0Paul Jensen * @hide 4582324373124f0ba4e59ba6d3de9e274f2fa28cff0Paul Jensen */ 4592324373124f0ba4e59ba6d3de9e274f2fa28cff0Paul Jensen @VisibleForTesting 4602324373124f0ba4e59ba6d3de9e274f2fa28cff0Paul Jensen public static final class CaptivePortalProbeResult { 461d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi static final CaptivePortalProbeResult FAILED = new CaptivePortalProbeResult(599); 462c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 463d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi private final int mHttpResponseCode; // HTTP response code returned from Internet probe. 464d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi final String redirectUrl; // Redirect destination returned from Internet probe. 465d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi final String detectUrl; // URL where a 204 response code indicates 466d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi // captive portal has been appeased. 4672324373124f0ba4e59ba6d3de9e274f2fa28cff0Paul Jensen 468d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi public CaptivePortalProbeResult( 469d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi int httpResponseCode, String redirectUrl, String detectUrl) { 4702324373124f0ba4e59ba6d3de9e274f2fa28cff0Paul Jensen mHttpResponseCode = httpResponseCode; 471d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi this.redirectUrl = redirectUrl; 472d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi this.detectUrl = detectUrl; 473d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi } 474d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi 475d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi public CaptivePortalProbeResult(int httpResponseCode) { 476d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi this(httpResponseCode, null, null); 4772324373124f0ba4e59ba6d3de9e274f2fa28cff0Paul Jensen } 478c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 479c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti boolean isSuccessful() { return mHttpResponseCode == 204; } 480c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti boolean isPortal() { 481c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti return !isSuccessful() && mHttpResponseCode >= 200 && mHttpResponseCode <= 399; 482c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti } 4832324373124f0ba4e59ba6d3de9e274f2fa28cff0Paul Jensen } 4842324373124f0ba4e59ba6d3de9e274f2fa28cff0Paul Jensen 48571b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen // Being in the EvaluatingState State indicates the Network is being evaluated for internet 486d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen // connectivity, or that the user has indicated that this network is unwanted. 487ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen private class EvaluatingState extends State { 488d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen private int mReevaluateDelayMs; 489d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen private int mAttempts; 490869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen 491ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen @Override 492ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen public void enter() { 493a488c23dd5c9e024fb8ec702cee722916cdeaf0eErik Kline // If we have already started to track time spent in EvaluatingState 494a488c23dd5c9e024fb8ec702cee722916cdeaf0eErik Kline // don't reset the timer due simply to, say, commands or events that 495a488c23dd5c9e024fb8ec702cee722916cdeaf0eErik Kline // cause us to exit and re-enter EvaluatingState. 496a488c23dd5c9e024fb8ec702cee722916cdeaf0eErik Kline if (!mEvaluationTimer.isStarted()) { 497a488c23dd5c9e024fb8ec702cee722916cdeaf0eErik Kline mEvaluationTimer.start(); 498a488c23dd5c9e024fb8ec702cee722916cdeaf0eErik Kline } 499ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); 5007ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen if (mUidResponsibleForReeval != INVALID_UID) { 5017ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen TrafficStats.setThreadStatsUid(mUidResponsibleForReeval); 5027ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen mUidResponsibleForReeval = INVALID_UID; 5037ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen } 504d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS; 505d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen mAttempts = 0; 506ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } 507ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 508ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen @Override 509ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen public boolean processMessage(Message message) { 510ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen switch (message.what) { 511ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen case CMD_REEVALUATE: 512d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen if (message.arg1 != mReevaluateToken || mUserDoesNotWant) 513d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen return HANDLED; 5142c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen // Don't bother validating networks that don't satisify the default request. 5152c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen // This includes: 5162c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen // - VPNs which can be considered explicitly desired by the user and the 5172c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen // user's desire trumps whether the network validates. 5182c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen // - Networks that don't provide internet access. It's unclear how to 5192c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen // validate such networks. 5202c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen // - Untrusted networks. It's unsafe to prompt the user to sign-in to 5212c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen // such networks and the user didn't express interest in connecting to 5222c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen // such networks (an app did) so the user may be unhappily surprised when 5232c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen // asked to sign-in to a network they didn't want to connect to in the 5242c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen // first place. Validation could be done to adjust the network scores 5252c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen // however these networks are app-requested and may not be intended for 5262c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen // general usage, in which case general validation may not be an accurate 5272c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen // measure of the network's quality. Only the app knows how to evaluate 5282c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen // the network so don't bother validating here. Furthermore sending HTTP 5292c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen // packets over the network may be undesirable, for example an extremely 5302c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen // expensive metered network, or unwanted leaking of the User Agent string. 5312c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen if (!mDefaultRequest.networkCapabilities.satisfiedByNetworkCapabilities( 5322c311d61eaf331818e601f97485f88c4cf26384dPaul Jensen mNetworkAgentInfo.networkCapabilities)) { 533c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti validationLog("Network would not satisfy default request, not validating"); 534ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen transitionTo(mValidatedState); 535d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen return HANDLED; 536ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } 537d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen mAttempts++; 538351bfad339ede00a81862b3b57234d7a32067279Lorenzo Colitti // Note: This call to isCaptivePortal() could take up to a minute. Resolving the 539351bfad339ede00a81862b3b57234d7a32067279Lorenzo Colitti // server's IP addresses could hit the DNS timeout, and attempting connections 540351bfad339ede00a81862b3b57234d7a32067279Lorenzo Colitti // to each of the server's several IP addresses (currently one IPv4 and one 541351bfad339ede00a81862b3b57234d7a32067279Lorenzo Colitti // IPv6) could each take SOCKET_TIMEOUT_MS. During this time this StateMachine 542351bfad339ede00a81862b3b57234d7a32067279Lorenzo Colitti // will be unresponsive. isCaptivePortal() could be executed on another Thread 543351bfad339ede00a81862b3b57234d7a32067279Lorenzo Colitti // if this is found to cause problems. 5442324373124f0ba4e59ba6d3de9e274f2fa28cff0Paul Jensen CaptivePortalProbeResult probeResult = isCaptivePortal(); 545c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti if (probeResult.isSuccessful()) { 546ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen transitionTo(mValidatedState); 547c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti } else if (probeResult.isPortal()) { 5482324373124f0ba4e59ba6d3de9e274f2fa28cff0Paul Jensen mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED, 549d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi NETWORK_TEST_RESULT_INVALID, mNetId, probeResult.redirectUrl)); 550d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi mLastPortalProbeResult = probeResult; 55171b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen transitionTo(mCaptivePortalState); 552d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen } else { 553d9be23fa4cafc41dcb8b5e2f090e3f2d91197bfbPaul Jensen final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); 554869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen sendMessageDelayed(msg, mReevaluateDelayMs); 555cfddd6879283860bb4d2cf2972ea086f585a37ecHugo Benichi logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED); 556d9be23fa4cafc41dcb8b5e2f090e3f2d91197bfbPaul Jensen mConnectivityServiceHandler.sendMessage(obtainMessage( 557a488c23dd5c9e024fb8ec702cee722916cdeaf0eErik Kline EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, mNetId, 558d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi probeResult.redirectUrl)); 559d9be23fa4cafc41dcb8b5e2f090e3f2d91197bfbPaul Jensen if (mAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) { 560d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen // Don't continue to blame UID forever. 561d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen TrafficStats.clearThreadStatsUid(); 562d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen } 563d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen mReevaluateDelayMs *= 2; 564d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen if (mReevaluateDelayMs > MAX_REEVALUATE_DELAY_MS) { 565d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen mReevaluateDelayMs = MAX_REEVALUATE_DELAY_MS; 566d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen } 567ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } 568d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen return HANDLED; 5697ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen case CMD_FORCE_REEVALUATION: 570d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen // Before IGNORE_REEVALUATE_ATTEMPTS attempts are made, 571d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen // ignore any re-evaluation requests. After, restart the 572d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen // evaluation process via EvaluatingState#enter. 573a488c23dd5c9e024fb8ec702cee722916cdeaf0eErik Kline return (mAttempts < IGNORE_REEVALUATE_ATTEMPTS) ? HANDLED : NOT_HANDLED; 574ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen default: 575ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen return NOT_HANDLED; 576ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } 577ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } 5787ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen 5797ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen @Override 5807ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen public void exit() { 5817ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen TrafficStats.clearThreadStatsUid(); 5827ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen } 583ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } 584ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 585dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen // BroadcastReceiver that waits for a particular Intent and then posts a message. 586dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen private class CustomIntentReceiver extends BroadcastReceiver { 58771b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen private final int mToken; 58871b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen private final int mWhat; 589dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen private final String mAction; 59071b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen CustomIntentReceiver(String action, int token, int what) { 59171b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen mToken = token; 59271b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen mWhat = what; 593dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen mAction = action + "_" + mNetworkAgentInfo.network.netId + "_" + token; 594dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen mContext.registerReceiver(this, new IntentFilter(mAction)); 595dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen } 596dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen public PendingIntent getPendingIntent() { 59725a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen final Intent intent = new Intent(mAction); 59825a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen intent.setPackage(mContext.getPackageName()); 59925a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen return PendingIntent.getBroadcast(mContext, 0, intent, 0); 600dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen } 601dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen @Override 602dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen public void onReceive(Context context, Intent intent) { 60371b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen if (intent.getAction().equals(mAction)) sendMessage(obtainMessage(mWhat, mToken)); 604869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } 605dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen } 606dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen 60771b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen // Being in the CaptivePortalState State indicates a captive portal was detected and the user 60871b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen // has been shown a notification to sign-in. 609ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen private class CaptivePortalState extends State { 61025a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen private static final String ACTION_LAUNCH_CAPTIVE_PORTAL_APP = 61125a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen "android.net.netmon.launchCaptivePortalApp"; 61225a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen 613ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen @Override 614ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen public void enter() { 615dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi maybeLogEvaluationResult( 616dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi networkEventType(validationStage(), EvaluationResult.CAPTIVE_PORTAL)); 617d0491e9a2c51f8b16f9e11627c38278e3649ae8cPaul Jensen // Don't annoy user with sign-in notifications. 618700f236afb373125353a304d9098557890b9253fPaul Jensen if (mDontDisplaySigninNotification) return; 61925a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen // Create a CustomIntentReceiver that sends us a 62025a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen // CMD_LAUNCH_CAPTIVE_PORTAL_APP message when the user 62125a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen // touches the notification. 62225a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen if (mLaunchCaptivePortalAppBroadcastReceiver == null) { 62371b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen // Wait for result. 62425a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen mLaunchCaptivePortalAppBroadcastReceiver = new CustomIntentReceiver( 62525a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen ACTION_LAUNCH_CAPTIVE_PORTAL_APP, new Random().nextInt(), 62625a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen CMD_LAUNCH_CAPTIVE_PORTAL_APP); 62771b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen } 62825a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen // Display the sign in notification. 62971b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1, 63071b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen mNetworkAgentInfo.network.netId, 63125a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen mLaunchCaptivePortalAppBroadcastReceiver.getPendingIntent()); 63271b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen mConnectivityServiceHandler.sendMessage(message); 633ee3e2ce4eea8a1788d91001224ef9f231c399b95Paul Jensen // Retest for captive portal occasionally. 634ee3e2ce4eea8a1788d91001224ef9f231c399b95Paul Jensen sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */, 635ee3e2ce4eea8a1788d91001224ef9f231c399b95Paul Jensen CAPTIVE_PORTAL_REEVALUATE_DELAY_MS); 636dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi mValidations++; 637ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } 638ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 639ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen @Override 640ee3e2ce4eea8a1788d91001224ef9f231c399b95Paul Jensen public void exit() { 6411bf6ec2f868041ba4c3d4c88d3ac482bfd4c52b9fionaxu removeMessages(CMD_CAPTIVE_PORTAL_RECHECK); 642ee3e2ce4eea8a1788d91001224ef9f231c399b95Paul Jensen } 643ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } 644ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 64592eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi private static String getCaptivePortalServerHttpsUrl(Context context) { 64692eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi return getSetting(context, Settings.Global.CAPTIVE_PORTAL_HTTPS_URL, DEFAULT_HTTPS_URL); 64792eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi } 64892eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi 64992eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi public static String getCaptivePortalServerHttpUrl(Context context) { 65092eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi return getSetting(context, Settings.Global.CAPTIVE_PORTAL_HTTP_URL, DEFAULT_HTTP_URL); 65192eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi } 65292eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi 65392eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi private static String getCaptivePortalFallbackUrl(Context context) { 65492eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi return getSetting(context, 65592eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL, DEFAULT_FALLBACK_URL); 65692eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi } 65792eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi 65892eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi private static String getCaptivePortalUserAgent(Context context) { 65992eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi return getSetting(context, Settings.Global.CAPTIVE_PORTAL_USER_AGENT, DEFAULT_USER_AGENT); 660c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti } 661c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 66292eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi private static String getSetting(Context context, String symbol, String defaultValue) { 66392eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi final String value = Settings.Global.getString(context.getContentResolver(), symbol); 66492eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi return value != null ? value : defaultValue; 665b7c2487c8b5fbd154643b8ddade8d88507cae137Udam Saini } 666b7c2487c8b5fbd154643b8ddade8d88507cae137Udam Saini 667cf4c2c637268b1a2979e20a8b5644916777a02a4Paul Jensen @VisibleForTesting 6682324373124f0ba4e59ba6d3de9e274f2fa28cff0Paul Jensen protected CaptivePortalProbeResult isCaptivePortal() { 6694bc78eba6f16ef84206aaed9edd5ca4bb4f6c420Calvin On if (!mIsCaptivePortalCheckEnabled) { 6704bc78eba6f16ef84206aaed9edd5ca4bb4f6c420Calvin On validationLog("Validation disabled."); 6714bc78eba6f16ef84206aaed9edd5ca4bb4f6c420Calvin On return new CaptivePortalProbeResult(204); 6724bc78eba6f16ef84206aaed9edd5ca4bb4f6c420Calvin On } 673ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen 67492eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi URL pacUrl = null, httpsUrl = null, httpUrl = null, fallbackUrl = null; 675c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 676c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // On networks with a PAC instead of fetching a URL that should result in a 204 677c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // response, we instead simply fetch the PAC script. This is done for a few reasons: 678c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // 1. At present our PAC code does not yet handle multiple PACs on multiple networks 679c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // until something like https://android-review.googlesource.com/#/c/115180/ lands. 680c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // Network.openConnection() will ignore network-specific PACs and instead fetch 681c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // using NO_PROXY. If a PAC is in place, the only fetch we know will succeed with 682c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // NO_PROXY is the fetch of the PAC itself. 683c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // 2. To proxy the generate_204 fetch through a PAC would require a number of things 684c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // happen before the fetch can commence, namely: 685c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // a) the PAC script be fetched 686c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // b) a PAC script resolver service be fired up and resolve the captive portal 687c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // server. 688c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // Network validation could be delayed until these prerequisities are satisifed or 689c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // could simply be left to race them. Neither is an optimal solution. 690c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // 3. PAC scripts are sometimes used to block or restrict Internet access and may in 691c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // fact block fetching of the generate_204 URL which would lead to false negative 692c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti // results for network validation. 693c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti final ProxyInfo proxyInfo = mNetworkAgentInfo.linkProperties.getHttpProxy(); 694c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) { 69592eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi pacUrl = makeURL(proxyInfo.getPacFileUrl().toString()); 69692eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi if (pacUrl == null) { 697c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti return CaptivePortalProbeResult.FAILED; 6982f0a8974d1f45aad590829042df8cff07e989635Paul Jensen } 699c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti } 700c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 701c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti if (pacUrl == null) { 70292eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi httpsUrl = makeURL(getCaptivePortalServerHttpsUrl(mContext)); 70392eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi httpUrl = makeURL(getCaptivePortalServerHttpUrl(mContext)); 70492eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi fallbackUrl = makeURL(getCaptivePortalFallbackUrl(mContext)); 70592eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi if (httpUrl == null || httpsUrl == null) { 706c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti return CaptivePortalProbeResult.FAILED; 7072f0a8974d1f45aad590829042df8cff07e989635Paul Jensen } 708c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti } 709c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 710c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti long startTime = SystemClock.elapsedRealtime(); 711c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 712eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi final CaptivePortalProbeResult result; 713c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti if (pacUrl != null) { 714eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi result = sendDnsAndHttpProbes(null, pacUrl, ValidationProbeEvent.PROBE_PAC); 715c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti } else if (mUseHttps) { 716eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi result = sendParallelHttpProbes(proxyInfo, httpsUrl, httpUrl, fallbackUrl); 717c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti } else { 718eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi result = sendDnsAndHttpProbes(proxyInfo, httpUrl, ValidationProbeEvent.PROBE_HTTP); 719c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti } 720c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 721c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti long endTime = SystemClock.elapsedRealtime(); 722c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 723c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti sendNetworkConditionsBroadcast(true /* response received */, 724c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti result.isPortal() /* isCaptivePortal */, 725c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti startTime, endTime); 726c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 727c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti return result; 728c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti } 729c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 730c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti /** 731eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi * Do a DNS resolution and URL fetch on a known web server to see if we get the data we expect. 732eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi * @return a CaptivePortalProbeResult inferred from the HTTP response. 733eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi */ 734eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi private CaptivePortalProbeResult sendDnsAndHttpProbes(ProxyInfo proxy, URL url, int probeType) { 735eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi // Pre-resolve the captive portal server host so we can log it. 736eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi // Only do this if HttpURLConnection is about to, to avoid any potentially 737eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi // unnecessary resolution. 738eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi final String host = (proxy != null) ? proxy.getHost() : url.getHost(); 739eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi sendDnsProbe(host); 740eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi return sendHttpProbe(url, probeType); 741eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi } 742eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi 743eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi /** Do a DNS resolution of the given server. */ 744eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi private void sendDnsProbe(String host) { 745eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi if (TextUtils.isEmpty(host)) { 746eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi return; 747eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi } 748eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi 749eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi final String name = ValidationProbeEvent.getProbeName(ValidationProbeEvent.PROBE_DNS); 750eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi final Stopwatch watch = new Stopwatch().start(); 751eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi int result; 752eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi String connectInfo; 753eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi try { 754eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi InetAddress[] addresses = mNetworkAgentInfo.network.getAllByName(host); 755eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi result = ValidationProbeEvent.DNS_SUCCESS; 756eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi StringBuffer buffer = new StringBuffer(host).append("="); 757eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi for (InetAddress address : addresses) { 758eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi buffer.append(address.getHostAddress()); 759eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi if (address != addresses[addresses.length-1]) buffer.append(","); 760eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi } 761eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi connectInfo = buffer.toString(); 762eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi } catch (UnknownHostException e) { 763eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi result = ValidationProbeEvent.DNS_FAILURE; 764eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi connectInfo = host; 765eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi } 766eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi final long latency = watch.stop(); 767eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi String resultString = (ValidationProbeEvent.DNS_SUCCESS == result) ? "OK" : "FAIL"; 768eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi validationLog(String.format("%s %s %dms, %s", name, resultString, latency, connectInfo)); 769eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result); 770eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi } 771eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi 772eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi /** 773eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi * Do a URL fetch on a known web server to see if we get the data we expect. 774eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi * @return a CaptivePortalProbeResult inferred from the HTTP response. 775c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti */ 776c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti @VisibleForTesting 777c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti protected CaptivePortalProbeResult sendHttpProbe(URL url, int probeType) { 778c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti HttpURLConnection urlConnection = null; 779c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti int httpResponseCode = 599; 780c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti String redirectUrl = null; 781c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti final Stopwatch probeTimer = new Stopwatch().start(); 782c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti try { 7839f1274b7e43d14c7e3a42148ebfda3905fec8b06Lorenzo Colitti urlConnection = (HttpURLConnection) mNetworkAgentInfo.network.openConnection(url); 784c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti urlConnection.setInstanceFollowRedirects(probeType == ValidationProbeEvent.PROBE_PAC); 785e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); 786e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); 787e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen urlConnection.setUseCaches(false); 78892eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi final String userAgent = getCaptivePortalUserAgent(mContext); 78992eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi if (userAgent != null) { 79092eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi urlConnection.setRequestProperty("User-Agent", userAgent); 79192eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi } 792306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen 793306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen // Time how long it takes to get a response to our request 794306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen long requestTimestamp = SystemClock.elapsedRealtime(); 795306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen 796be12d76feb1ba55797b408e5fa13c9f9ca1e06adPierre Imai httpResponseCode = urlConnection.getResponseCode(); 7972324373124f0ba4e59ba6d3de9e274f2fa28cff0Paul Jensen redirectUrl = urlConnection.getHeaderField("location"); 798306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen 799306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen // Time how long it takes to get a response to our request 800306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen long responseTimestamp = SystemClock.elapsedRealtime(); 801306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen 802c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti validationLog(ValidationProbeEvent.getProbeName(probeType) + " " + url + 803c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti " time=" + (responseTimestamp - requestTimestamp) + "ms" + 804c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti " ret=" + httpResponseCode + 80522b4c6a027d72ec90dc91d150bee007cb8167eedRobert Greenwalt " headers=" + urlConnection.getHeaderFields()); 806e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive 807e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen // portal. The only example of this seen so far was a captive portal. For 808e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen // the time being go with prior behavior of assuming it's not a captive 809e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen // portal. If it is considered a captive portal, a different sign-in URL 810e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen // is needed (i.e. can't browse a 204). This could be the result of an HTTP 811e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen // proxy server. 812cb4aa4d412c7940386df9b8dd681e0d2efebfd1dHugo Benichi if (httpResponseCode == 200) { 813cb4aa4d412c7940386df9b8dd681e0d2efebfd1dHugo Benichi if (probeType == ValidationProbeEvent.PROBE_PAC) { 814cb4aa4d412c7940386df9b8dd681e0d2efebfd1dHugo Benichi validationLog("PAC fetch 200 response interpreted as 204 response."); 815cb4aa4d412c7940386df9b8dd681e0d2efebfd1dHugo Benichi httpResponseCode = 204; 816cb4aa4d412c7940386df9b8dd681e0d2efebfd1dHugo Benichi } else if (urlConnection.getContentLengthLong() == 0) { 817cb4aa4d412c7940386df9b8dd681e0d2efebfd1dHugo Benichi // Consider 200 response with "Content-length=0" to not be a captive portal. 818cb4aa4d412c7940386df9b8dd681e0d2efebfd1dHugo Benichi // There's no point in considering this a captive portal as the user cannot 819cb4aa4d412c7940386df9b8dd681e0d2efebfd1dHugo Benichi // sign-in to an empty page. Probably the result of a broken transparent proxy. 820cb4aa4d412c7940386df9b8dd681e0d2efebfd1dHugo Benichi // See http://b/9972012. 821cb4aa4d412c7940386df9b8dd681e0d2efebfd1dHugo Benichi validationLog( 822cb4aa4d412c7940386df9b8dd681e0d2efebfd1dHugo Benichi "200 response with Content-length=0 interpreted as 204 response."); 823cb4aa4d412c7940386df9b8dd681e0d2efebfd1dHugo Benichi httpResponseCode = 204; 824cb4aa4d412c7940386df9b8dd681e0d2efebfd1dHugo Benichi } else if (urlConnection.getContentLengthLong() == -1) { 825cb4aa4d412c7940386df9b8dd681e0d2efebfd1dHugo Benichi // When no Content-length (default value == -1), attempt to read a byte from the 826cb4aa4d412c7940386df9b8dd681e0d2efebfd1dHugo Benichi // response. Do not use available() as it is unreliable. See http://b/33498325. 827cb4aa4d412c7940386df9b8dd681e0d2efebfd1dHugo Benichi if (urlConnection.getInputStream().read() == -1) { 828cb4aa4d412c7940386df9b8dd681e0d2efebfd1dHugo Benichi validationLog("Empty 200 response interpreted as 204 response."); 829cb4aa4d412c7940386df9b8dd681e0d2efebfd1dHugo Benichi httpResponseCode = 204; 830cb4aa4d412c7940386df9b8dd681e0d2efebfd1dHugo Benichi } 831cb4aa4d412c7940386df9b8dd681e0d2efebfd1dHugo Benichi } 8328fe1742946e92dfe7e01e6a06f48ad626c01bd35Paul Jensen } 833ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } catch (IOException e) { 83422b4c6a027d72ec90dc91d150bee007cb8167eedRobert Greenwalt validationLog("Probably not a portal: exception " + e); 835869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen if (httpResponseCode == 599) { 836869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen // TODO: Ping gateway and DNS server and log results. 837869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } 838ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } finally { 839ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen if (urlConnection != null) { 840ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen urlConnection.disconnect(); 841ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } 842ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } 843cfddd6879283860bb4d2cf2972ea086f585a37ecHugo Benichi logValidationProbe(probeTimer.stop(), probeType, httpResponseCode); 844d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi return new CaptivePortalProbeResult(httpResponseCode, redirectUrl, url.toString()); 845ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen } 846306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen 847d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi private CaptivePortalProbeResult sendParallelHttpProbes( 848eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi ProxyInfo proxy, URL httpsUrl, URL httpUrl, URL fallbackUrl) { 849d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi // Number of probes to wait for. If a probe completes with a conclusive answer 850d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi // it shortcuts the latch immediately by forcing the count to 0. 851c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti final CountDownLatch latch = new CountDownLatch(2); 852c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 853c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti final class ProbeThread extends Thread { 854c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti private final boolean mIsHttps; 855d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi private volatile CaptivePortalProbeResult mResult = CaptivePortalProbeResult.FAILED; 856c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 857c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti public ProbeThread(boolean isHttps) { 858c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti mIsHttps = isHttps; 859c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti } 860c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 861d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi public CaptivePortalProbeResult result() { 862c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti return mResult; 863c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti } 864c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 865c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti @Override 866c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti public void run() { 867c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti if (mIsHttps) { 868eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi mResult = 869eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi sendDnsAndHttpProbes(proxy, httpsUrl, ValidationProbeEvent.PROBE_HTTPS); 870c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti } else { 871eb5e9aa2cea166455f4f157cf550e44626ede204Hugo Benichi mResult = sendDnsAndHttpProbes(proxy, httpUrl, ValidationProbeEvent.PROBE_HTTP); 872c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti } 873c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti if ((mIsHttps && mResult.isSuccessful()) || (!mIsHttps && mResult.isPortal())) { 874d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi // Stop waiting immediately if https succeeds or if http finds a portal. 875d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi while (latch.getCount() > 0) { 876d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi latch.countDown(); 877d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi } 878c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti } 879d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi // Signal this probe has completed. 880c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti latch.countDown(); 881c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti } 882c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti } 883c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 884d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi final ProbeThread httpsProbe = new ProbeThread(true); 885d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi final ProbeThread httpProbe = new ProbeThread(false); 886c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 887c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti try { 888d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi httpsProbe.start(); 889d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi httpProbe.start(); 890d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi latch.await(PROBE_TIMEOUT_MS, TimeUnit.MILLISECONDS); 891c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti } catch (InterruptedException e) { 892d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi validationLog("Error: probes wait interrupted!"); 893c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti return CaptivePortalProbeResult.FAILED; 894c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti } 895c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 896d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi final CaptivePortalProbeResult httpsResult = httpsProbe.result(); 897d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi final CaptivePortalProbeResult httpResult = httpProbe.result(); 898c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 899d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi // Look for a conclusive probe result first. 900d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi if (httpResult.isPortal()) { 901d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi return httpResult; 902d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi } 903d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi // httpsResult.isPortal() is not expected, but check it nonetheless. 904d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi if (httpsResult.isPortal() || httpsResult.isSuccessful()) { 905d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi return httpsResult; 906d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi } 907d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi // If a fallback url is specified, use a fallback probe to try again portal detection. 908d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi if (fallbackUrl != null) { 909d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi CaptivePortalProbeResult result = 910d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi sendHttpProbe(fallbackUrl, ValidationProbeEvent.PROBE_FALLBACK); 911d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi if (result.isPortal()) { 912d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi return result; 913d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi } 914d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi } 915d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi // Otherwise wait until https probe completes and use its result. 916d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi try { 917d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi httpsProbe.join(); 918d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi } catch (InterruptedException e) { 919d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi validationLog("Error: https probe wait interrupted!"); 920d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi return CaptivePortalProbeResult.FAILED; 921d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi } 922d953bf853d233026c9064e6a0c962b14cf4658d6Hugo Benichi return httpsProbe.result(); 923c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti } 924c5be12e7ac43cbe8c68219fa702c6fb7f06183c3Lorenzo Colitti 92592eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi private URL makeURL(String url) { 92692eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi if (url != null) { 92792eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi try { 92892eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi return new URL(url); 92992eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi } catch (MalformedURLException e) { 93092eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi validationLog("Bad URL: " + url); 93192eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi } 93292eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi } 93392eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi return null; 93492eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi } 93592eb22fdd2e5e2d99ca0b2c14e68dfd632323a90Hugo Benichi 936306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen /** 937306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen * @param responseReceived - whether or not we received a valid HTTP response to our request. 938306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen * If false, isCaptivePortal and responseTimestampMs are ignored 939306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen * TODO: This should be moved to the transports. The latency could be passed to the transports 940306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen * along with the captive portal result. Currently the TYPE_MOBILE broadcasts appear unused so 941306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen * perhaps this could just be added to the WiFi transport only. 942306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen */ 943306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal, 944306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen long requestTimestampMs, long responseTimestampMs) { 945306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen if (Settings.Global.getInt(mContext.getContentResolver(), 946306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0) { 947306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen return; 948306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen } 949306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen 950fb68f8fbe0213841f393f8bdb5313e4e44f4f116Robert Greenwalt if (systemReady == false) return; 951fb68f8fbe0213841f393f8bdb5313e4e44f4f116Robert Greenwalt 952306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen Intent latencyBroadcast = new Intent(ACTION_NETWORK_CONDITIONS_MEASURED); 953306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen switch (mNetworkAgentInfo.networkInfo.getType()) { 954306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen case ConnectivityManager.TYPE_WIFI: 955306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo(); 956306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen if (currentWifiInfo != null) { 957306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not 958306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen // surrounded by double quotation marks (thus violating the Javadoc), but this 959306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen // was changed to match the Javadoc in API 17. Since clients may have started 960306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen // sanitizing the output of this method since API 17 was released, we should 961306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen // not change it here as it would become impossible to tell whether the SSID is 962306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen // simply being surrounded by quotes due to the API, or whether those quotes 963306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen // are actually part of the SSID. 964306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen latencyBroadcast.putExtra(EXTRA_SSID, currentWifiInfo.getSSID()); 965306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen latencyBroadcast.putExtra(EXTRA_BSSID, currentWifiInfo.getBSSID()); 966306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen } else { 967a4f17bcbdf787852267a6f24d83ca2011f0decacHugo Benichi if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found"); 968306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen return; 969306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen } 970306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen break; 971306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen case ConnectivityManager.TYPE_MOBILE: 972306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen latencyBroadcast.putExtra(EXTRA_NETWORK_TYPE, mTelephonyManager.getNetworkType()); 973306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen List<CellInfo> info = mTelephonyManager.getAllCellInfo(); 974306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen if (info == null) return; 975306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen int numRegisteredCellInfo = 0; 976306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen for (CellInfo cellInfo : info) { 977306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen if (cellInfo.isRegistered()) { 978306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen numRegisteredCellInfo++; 979306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen if (numRegisteredCellInfo > 1) { 980a4f17bcbdf787852267a6f24d83ca2011f0decacHugo Benichi if (VDBG) logw("more than one registered CellInfo." + 981a4f17bcbdf787852267a6f24d83ca2011f0decacHugo Benichi " Can't tell which is active. Bailing."); 982306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen return; 983306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen } 984306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen if (cellInfo instanceof CellInfoCdma) { 985306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity(); 986306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId); 987306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen } else if (cellInfo instanceof CellInfoGsm) { 988306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity(); 989306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId); 990306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen } else if (cellInfo instanceof CellInfoLte) { 991306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity(); 992306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId); 993306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen } else if (cellInfo instanceof CellInfoWcdma) { 994306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity(); 995306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId); 996306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen } else { 997a4f17bcbdf787852267a6f24d83ca2011f0decacHugo Benichi if (VDBG) logw("Registered cellinfo is unrecognized"); 998306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen return; 999306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen } 1000306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen } 1001306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen } 1002306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen break; 1003306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen default: 1004306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen return; 1005306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen } 1006306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen latencyBroadcast.putExtra(EXTRA_CONNECTIVITY_TYPE, mNetworkAgentInfo.networkInfo.getType()); 1007306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen latencyBroadcast.putExtra(EXTRA_RESPONSE_RECEIVED, responseReceived); 1008306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen latencyBroadcast.putExtra(EXTRA_REQUEST_TIMESTAMP_MS, requestTimestampMs); 1009306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen 1010306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen if (responseReceived) { 1011306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen latencyBroadcast.putExtra(EXTRA_IS_CAPTIVE_PORTAL, isCaptivePortal); 1012306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen latencyBroadcast.putExtra(EXTRA_RESPONSE_TIMESTAMP_MS, responseTimestampMs); 1013306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen } 101455298581a0750505bc7da0539fe94255f95326a4Paul Jensen mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT, 101555298581a0750505bc7da0539fe94255f95326a4Paul Jensen PERMISSION_ACCESS_NETWORK_CONDITIONS); 1016306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen } 1017d7b6ca91e9ecac15949a4484d560cfab5833a431Paul Jensen 1018cfddd6879283860bb4d2cf2972ea086f585a37ecHugo Benichi private void logNetworkEvent(int evtype) { 1019cfddd6879283860bb4d2cf2972ea086f585a37ecHugo Benichi mMetricsLog.log(new NetworkEvent(mNetId, evtype)); 1020cfddd6879283860bb4d2cf2972ea086f585a37ecHugo Benichi } 1021cfddd6879283860bb4d2cf2972ea086f585a37ecHugo Benichi 1022dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi private int networkEventType(ValidationStage s, EvaluationResult r) { 1023dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi if (s.isFirstValidation) { 1024dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi if (r.isValidated) { 1025dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi return NetworkEvent.NETWORK_FIRST_VALIDATION_SUCCESS; 1026dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi } else { 1027dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi return NetworkEvent.NETWORK_FIRST_VALIDATION_PORTAL_FOUND; 1028dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi } 1029dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi } else { 1030dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi if (r.isValidated) { 1031dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi return NetworkEvent.NETWORK_REVALIDATION_SUCCESS; 1032dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi } else { 1033dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi return NetworkEvent.NETWORK_REVALIDATION_PORTAL_FOUND; 1034dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi } 1035dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi } 1036dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi } 1037dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi 1038cfddd6879283860bb4d2cf2972ea086f585a37ecHugo Benichi private void maybeLogEvaluationResult(int evtype) { 1039cfddd6879283860bb4d2cf2972ea086f585a37ecHugo Benichi if (mEvaluationTimer.isRunning()) { 1040cfddd6879283860bb4d2cf2972ea086f585a37ecHugo Benichi mMetricsLog.log(new NetworkEvent(mNetId, evtype, mEvaluationTimer.stop())); 1041cfddd6879283860bb4d2cf2972ea086f585a37ecHugo Benichi mEvaluationTimer.reset(); 1042cfddd6879283860bb4d2cf2972ea086f585a37ecHugo Benichi } 1043cfddd6879283860bb4d2cf2972ea086f585a37ecHugo Benichi } 1044cfddd6879283860bb4d2cf2972ea086f585a37ecHugo Benichi 1045cfddd6879283860bb4d2cf2972ea086f585a37ecHugo Benichi private void logValidationProbe(long durationMs, int probeType, int probeResult) { 1046dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi probeType = 1047dd22982e250d1758098a88f00e607a464928ae5aHugo Benichi ValidationProbeEvent.makeProbeType(probeType, validationStage().isFirstValidation); 1048cfddd6879283860bb4d2cf2972ea086f585a37ecHugo Benichi mMetricsLog.log(new ValidationProbeEvent(mNetId, durationMs, probeType, probeResult)); 1049cfddd6879283860bb4d2cf2972ea086f585a37ecHugo Benichi } 1050ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen} 1051