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
1979a08051c5588d8420656813b21993d490e93dd0Paul Jensenimport android.app.AlarmManager;
20869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.app.PendingIntent;
21869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.content.BroadcastReceiver;
22869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.content.ComponentName;
23ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport android.content.Context;
24869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.content.Intent;
25869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.content.IntentFilter;
26869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.net.ConnectivityManager;
27869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.net.Network;
28ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport android.net.NetworkCapabilities;
29ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport android.net.NetworkInfo;
307ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensenimport android.net.TrafficStats;
31306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.net.wifi.WifiInfo;
32306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.net.wifi.WifiManager;
33ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport android.os.Handler;
34ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport android.os.Message;
35306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.os.SystemClock;
36ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport android.os.SystemProperties;
37869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.os.UserHandle;
38ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport android.provider.Settings;
39306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.telephony.CellIdentityCdma;
40306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.telephony.CellIdentityGsm;
41306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.telephony.CellIdentityLte;
42306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.telephony.CellIdentityWcdma;
43306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.telephony.CellInfo;
44306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.telephony.CellInfoCdma;
45306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.telephony.CellInfoGsm;
46306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.telephony.CellInfoLte;
47306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.telephony.CellInfoWcdma;
48306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport android.telephony.TelephonyManager;
49ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
50ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport com.android.internal.util.Protocol;
51ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport com.android.internal.util.State;
52ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport com.android.internal.util.StateMachine;
53869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport com.android.server.ConnectivityService;
54ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport com.android.server.connectivity.NetworkAgentInfo;
55ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
56ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport java.io.IOException;
57ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport java.net.HttpURLConnection;
58ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenimport java.net.URL;
59306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensenimport java.util.List;
60ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
61ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen/**
62ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen * {@hide}
63ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen */
64ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensenpublic class NetworkMonitor extends StateMachine {
65ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private static final boolean DBG = true;
66ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private static final String TAG = "NetworkMonitor";
67ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private static final String DEFAULT_SERVER = "clients3.google.com";
68ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private static final int SOCKET_TIMEOUT_MS = 10000;
69306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen    public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
70306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen            "android.net.conn.NETWORK_CONDITIONS_MEASURED";
71306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen    public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type";
72306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen    public static final String EXTRA_NETWORK_TYPE = "extra_network_type";
73306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen    public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received";
74306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen    public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal";
75306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen    public static final String EXTRA_CELL_ID = "extra_cellid";
76306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen    public static final String EXTRA_SSID = "extra_ssid";
77306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen    public static final String EXTRA_BSSID = "extra_bssid";
78306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen    /** real time since boot */
79306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen    public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms";
80306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen    public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms";
81306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen
82306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen    private static final String PERMISSION_ACCESS_NETWORK_CONDITIONS =
83306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen            "android.permission.ACCESS_NETWORK_CONDITIONS";
84ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
85869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen    // Keep these in sync with CaptivePortalLoginActivity.java.
86869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen    // Intent broadcast from CaptivePortalLogin indicating sign-in is complete.
87869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen    // Extras:
88869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen    //     EXTRA_TEXT       = netId
89869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen    //     LOGGED_IN_RESULT = "1" if we should use network, "0" if not.
90869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen    private static final String ACTION_CAPTIVE_PORTAL_LOGGED_IN =
91869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            "android.net.netmon.captive_portal_logged_in";
92869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen    private static final String LOGGED_IN_RESULT = "result";
93869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen
94ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen    // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
95ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen    // The network should be used as a default internet connection.  It was found to be:
96ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen    // 1. a functioning network providing internet access, or
97ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen    // 2. a captive portal and the user decided to use it as is.
98ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen    public static final int NETWORK_TEST_RESULT_VALID = 0;
99ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen    // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
100ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen    // The network should not be used as a default internet connection.  It was found to be:
101ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen    // 1. a captive portal and the user is prompted to sign-in, or
102ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen    // 2. a captive portal and the user did not want to use it, or
103ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen    // 3. a broken network (e.g. DNS failed, connect failed, HTTP request failed).
104ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen    public static final int NETWORK_TEST_RESULT_INVALID = 1;
105ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen
106ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private static final int BASE = Protocol.BASE_NETWORK_MONITOR;
107ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
108ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    /**
109ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * Inform NetworkMonitor that their network is connected.
110ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * Initiates Network Validation.
111ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     */
112ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    public static final int CMD_NETWORK_CONNECTED = BASE + 1;
113ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
114ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    /**
115ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen     * Inform ConnectivityService that the network has been tested.
116ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * obj = NetworkAgentInfo
117ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen     * arg1 = One of the NETWORK_TESTED_RESULT_* constants.
118ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     */
119ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen    public static final int EVENT_NETWORK_TESTED = BASE + 2;
120ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
121ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    /**
122ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * Inform NetworkMonitor to linger a network.  The Monitor should
123ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * start a timer and/or start watching for zero live connections while
124ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * moving towards LINGER_COMPLETE.  After the Linger period expires
125ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * (or other events mark the end of the linger state) the LINGER_COMPLETE
126ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * event should be sent and the network will be shut down.  If a
127ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * CMD_NETWORK_CONNECTED happens before the LINGER completes
128ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * it indicates further desire to keep the network alive and so
129ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * the LINGER is aborted.
130ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     */
131ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    public static final int CMD_NETWORK_LINGER = BASE + 3;
132ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
133ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    /**
134ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * Message to self indicating linger delay has expired.
135ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * arg1 = Token to ignore old messages.
136ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     */
137ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private static final int CMD_LINGER_EXPIRED = BASE + 4;
138ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
139ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    /**
140ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * Inform ConnectivityService that the network LINGER period has
141ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * expired.
142ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * obj = NetworkAgentInfo
143ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     */
144ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    public static final int EVENT_NETWORK_LINGER_COMPLETE = BASE + 5;
145ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
146ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    /**
147ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * Message to self indicating it's time to evaluate a network's connectivity.
148ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * arg1 = Token to ignore old messages.
149ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     */
150869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen    private static final int CMD_REEVALUATE = BASE + 6;
151ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
152ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    /**
153ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * Inform NetworkMonitor that the network has disconnected.
154ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     */
1557ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen    public static final int CMD_NETWORK_DISCONNECTED = BASE + 7;
156ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
157ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    /**
158ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * Force evaluation even if it has succeeded in the past.
1597ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen     * arg1 = UID responsible for requesting this reeval.  Will be billed for data.
160ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     */
1617ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen    public static final int CMD_FORCE_REEVALUATION = BASE + 8;
162869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen
163869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen    /**
164869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen     * Message to self indicating captive portal login is complete.
165869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen     * arg1 = Token to ignore old messages.
166869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen     * arg2 = 1 if we should use this network, 0 otherwise.
167869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen     */
1687ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen    private static final int CMD_CAPTIVE_PORTAL_LOGGED_IN = BASE + 9;
169869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen
170869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen    /**
171869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen     * Message to self indicating user desires to log into captive portal.
172869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen     * arg1 = Token to ignore old messages.
173869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen     */
1747ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen    private static final int CMD_USER_WANTS_SIGN_IN = BASE + 10;
175869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen
176869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen    /**
177869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen     * Request ConnectivityService display provisioning notification.
178869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen     * arg1    = Whether to make the notification visible.
179fdc4e4af7305514359e041dbec742f4b6561e393Paul Jensen     * arg2    = NetID.
180fdc4e4af7305514359e041dbec742f4b6561e393Paul Jensen     * obj     = Intent to be launched when notification selected by user, null if !arg1.
181869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen     */
1827ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen    public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 11;
183869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen
184869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen    /**
185869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen     * Message to self indicating sign-in app bypassed captive portal.
186869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen     */
1877ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen    private static final int EVENT_APP_BYPASSED_CAPTIVE_PORTAL = BASE + 12;
188869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen
189869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen    /**
190869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen     * Message to self indicating no sign-in app responded.
191869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen     */
1927ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen    private static final int EVENT_NO_APP_RESPONSE = BASE + 13;
193869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen
194869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen    /**
195869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen     * Message to self indicating sign-in app indicates sign-in is not possible.
196869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen     */
1977ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen    private static final int EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE = BASE + 14;
198ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
199ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
200ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    // Default to 30s linger time-out.
201ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private static final int DEFAULT_LINGER_DELAY_MS = 30000;
202ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private final int mLingerDelayMs;
203ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private int mLingerToken = 0;
204ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
205ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    // Negative values disable reevaluation.
206ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private static final String REEVALUATE_DELAY_PROPERTY = "persist.netmon.reeval_delay";
207ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    // Default to 5s reevaluation delay.
208ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private static final int DEFAULT_REEVALUATE_DELAY_MS = 5000;
209869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen    private static final int MAX_RETRIES = 10;
210ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private final int mReevaluateDelayMs;
211ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private int mReevaluateToken = 0;
2127ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen    private static final int INVALID_UID = -1;
2137ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen    private int mUidResponsibleForReeval = INVALID_UID;
214ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
215869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen    private int mCaptivePortalLoggedInToken = 0;
216869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen    private int mUserPromptedToken = 0;
217869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen
218ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private final Context mContext;
219ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private final Handler mConnectivityServiceHandler;
220ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private final NetworkAgentInfo mNetworkAgentInfo;
221306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen    private final TelephonyManager mTelephonyManager;
222306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen    private final WifiManager mWifiManager;
22379a08051c5588d8420656813b21993d490e93dd0Paul Jensen    private final AlarmManager mAlarmManager;
224ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
225ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private String mServer;
226ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private boolean mIsCaptivePortalCheckEnabled = false;
227ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
228ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen    // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app.
229ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen    private boolean mUserDoesNotWant = false;
230ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen
231fb68f8fbe0213841f393f8bdb5313e4e44f4f116Robert Greenwalt    public boolean systemReady = false;
232fb68f8fbe0213841f393f8bdb5313e4e44f4f116Robert Greenwalt
233ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private State mDefaultState = new DefaultState();
234ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private State mOfflineState = new OfflineState();
235ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private State mValidatedState = new ValidatedState();
236ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private State mEvaluatingState = new EvaluatingState();
237869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen    private State mUserPromptedState = new UserPromptedState();
238ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private State mCaptivePortalState = new CaptivePortalState();
239ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private State mLingeringState = new LingeringState();
240ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
241ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo) {
242ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        // Add suffix indicating which NetworkMonitor we're talking about.
243ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        super(TAG + networkAgentInfo.name());
244ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
245ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        mContext = context;
246ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        mConnectivityServiceHandler = handler;
247ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        mNetworkAgentInfo = networkAgentInfo;
248306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
249306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
25079a08051c5588d8420656813b21993d490e93dd0Paul Jensen        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
251ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
252ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        addState(mDefaultState);
253ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        addState(mOfflineState, mDefaultState);
254ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        addState(mValidatedState, mDefaultState);
255ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        addState(mEvaluatingState, mDefaultState);
256869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen        addState(mUserPromptedState, mDefaultState);
257ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        addState(mCaptivePortalState, mDefaultState);
258ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        addState(mLingeringState, mDefaultState);
25949f63fbed4cd84f5da182c85e8b999037dc64f3bRobert Greenwalt        setInitialState(mDefaultState);
260ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
261ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        mServer = Settings.Global.getString(mContext.getContentResolver(),
262ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                Settings.Global.CAPTIVE_PORTAL_SERVER);
263ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        if (mServer == null) mServer = DEFAULT_SERVER;
264ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
265ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        mLingerDelayMs = SystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
266ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        mReevaluateDelayMs = SystemProperties.getInt(REEVALUATE_DELAY_PROPERTY,
267ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                DEFAULT_REEVALUATE_DELAY_MS);
268ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
269869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen        mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),
270869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;
271ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
272ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        start();
273ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    }
274ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
275ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private class DefaultState extends State {
276ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        @Override
277ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        public boolean processMessage(Message message) {
278ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            if (DBG) log(getName() + message.toString());
279ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            switch (message.what) {
280ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                case CMD_NETWORK_LINGER:
281ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    if (DBG) log("Lingering");
282ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    transitionTo(mLingeringState);
283d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen                    return HANDLED;
284ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                case CMD_NETWORK_CONNECTED:
285ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    if (DBG) log("Connected");
286ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    transitionTo(mEvaluatingState);
287d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen                    return HANDLED;
288ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                case CMD_NETWORK_DISCONNECTED:
2891fd9aeef08fac363ec3ef2eb61cea519a04c51fdRobert Greenwalt                    if (DBG) log("Disconnected - quitting");
2901fd9aeef08fac363ec3ef2eb61cea519a04c51fdRobert Greenwalt                    quit();
291d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen                    return HANDLED;
292ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                case CMD_FORCE_REEVALUATION:
293ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    if (DBG) log("Forcing reevaluation");
2947ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen                    mUidResponsibleForReeval = message.arg1;
295ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    transitionTo(mEvaluatingState);
296d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen                    return HANDLED;
297ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                default:
298d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen                    return HANDLED;
299ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            }
300ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        }
301ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    }
302ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
303ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private class OfflineState extends State {
304ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        @Override
305ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen        public void enter() {
306ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen            mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
307ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen                    NETWORK_TEST_RESULT_INVALID, 0, mNetworkAgentInfo));
308ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen        }
309ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen
310ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen        @Override
311ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        public boolean processMessage(Message message) {
312ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            if (DBG) log(getName() + message.toString());
313ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen                        switch (message.what) {
314ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen                case CMD_FORCE_REEVALUATION:
315ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen                    // If the user has indicated they explicitly do not want to use this network,
316ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen                    // don't allow a reevaluation as this will be pointless and could result in
317ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen                    // the user being annoyed with repeated unwanted notifications.
318ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen                    return mUserDoesNotWant ? HANDLED : NOT_HANDLED;
319ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen                default:
320ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen                    return NOT_HANDLED;
321ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen            }
322ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        }
323ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    }
324ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
325ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private class ValidatedState extends State {
326ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        @Override
327ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        public void enter() {
328ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            if (DBG) log("Validated");
329ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen            mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
330ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen                    NETWORK_TEST_RESULT_VALID, 0, mNetworkAgentInfo));
331ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        }
332ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
333ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        @Override
334ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        public boolean processMessage(Message message) {
335ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            if (DBG) log(getName() + message.toString());
336ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            switch (message.what) {
337ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                case CMD_NETWORK_CONNECTED:
338ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    transitionTo(mValidatedState);
339d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen                    return HANDLED;
340ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                default:
341ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    return NOT_HANDLED;
342ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            }
343ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        }
344ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    }
345ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
346ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private class EvaluatingState extends State {
347869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen        private int mRetries;
348869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen
349ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        @Override
350ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        public void enter() {
351869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            mRetries = 0;
352ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
3537ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen            if (mUidResponsibleForReeval != INVALID_UID) {
3547ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen                TrafficStats.setThreadStatsUid(mUidResponsibleForReeval);
3557ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen                mUidResponsibleForReeval = INVALID_UID;
3567ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen            }
357ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        }
358ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
359ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        @Override
360ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        public boolean processMessage(Message message) {
361ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            if (DBG) log(getName() + message.toString());
362ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            switch (message.what) {
363ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                case CMD_REEVALUATE:
364ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    if (message.arg1 != mReevaluateToken)
365d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen                        return HANDLED;
3666bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen                    if (mNetworkAgentInfo.isVPN()) {
3676bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen                        transitionTo(mValidatedState);
368d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen                        return HANDLED;
3696bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen                    }
370ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    // If network provides no internet connectivity adjust evaluation.
371869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                    if (!mNetworkAgentInfo.networkCapabilities.hasCapability(
372ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                            NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
373ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                        // TODO: Try to verify something works.  Do all gateways respond to pings?
374ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                        transitionTo(mValidatedState);
375d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen                        return HANDLED;
376ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    }
3777ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen                    int httpResponseCode = isCaptivePortal();
378ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    if (httpResponseCode == 204) {
379ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                        transitionTo(mValidatedState);
380ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    } else if (httpResponseCode >= 200 && httpResponseCode <= 399) {
381a68d21350266a6abc16aeb3fec84eafd85b77cc8Paul Jensen                        transitionTo(mUserPromptedState);
382869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                    } else if (++mRetries > MAX_RETRIES) {
383869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                        transitionTo(mOfflineState);
384869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                    } else if (mReevaluateDelayMs >= 0) {
385869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                        Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
386869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                        sendMessageDelayed(msg, mReevaluateDelayMs);
387ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    }
388d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen                    return HANDLED;
3897ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen                case CMD_FORCE_REEVALUATION:
3907ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen                    // Ignore duplicate requests.
3917ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen                    return HANDLED;
392ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                default:
393ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    return NOT_HANDLED;
394ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            }
395ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        }
3967ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen
3977ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen        @Override
3987ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen        public void exit() {
3997ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen            TrafficStats.clearThreadStatsUid();
4007ccd3dfd53d8d45c447398ff137f052865dfd3b3Paul Jensen        }
401ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    }
402ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
403dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen    // BroadcastReceiver that waits for a particular Intent and then posts a message.
404dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen    private class CustomIntentReceiver extends BroadcastReceiver {
405dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen        private final Message mMessage;
406dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen        private final String mAction;
407dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen        CustomIntentReceiver(String action, int token, int message) {
408dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen            mMessage = obtainMessage(message, token);
409dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen            mAction = action + "_" + mNetworkAgentInfo.network.netId + "_" + token;
410dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen            mContext.registerReceiver(this, new IntentFilter(mAction));
411dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen        }
412dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen        public PendingIntent getPendingIntent() {
413dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen            return PendingIntent.getBroadcast(mContext, 0, new Intent(mAction), 0);
414dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen        }
415dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen        @Override
416dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen        public void onReceive(Context context, Intent intent) {
417dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen            if (intent.getAction().equals(mAction)) sendMessage(mMessage);
418869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen        }
419dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen    }
420dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen
421dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen    private class UserPromptedState extends State {
422dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen        // Intent broadcast when user selects sign-in notification.
423dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen        private static final String ACTION_SIGN_IN_REQUESTED =
424dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen                "android.net.netmon.sign_in_requested";
425869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen
426dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen        private CustomIntentReceiver mUserRespondedBroadcastReceiver;
427869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen
428869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen        @Override
429869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen        public void enter() {
430ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen            mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
431ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen                    NETWORK_TEST_RESULT_INVALID, 0, mNetworkAgentInfo));
432869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            // Wait for user to select sign-in notifcation.
433dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen            mUserRespondedBroadcastReceiver = new CustomIntentReceiver(ACTION_SIGN_IN_REQUESTED,
434dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen                    ++mUserPromptedToken, CMD_USER_WANTS_SIGN_IN);
435869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            // Initiate notification to sign-in.
436fdc4e4af7305514359e041dbec742f4b6561e393Paul Jensen            Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1,
437fdc4e4af7305514359e041dbec742f4b6561e393Paul Jensen                    mNetworkAgentInfo.network.netId,
438dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen                    mUserRespondedBroadcastReceiver.getPendingIntent());
439869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            mConnectivityServiceHandler.sendMessage(message);
440869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen        }
441869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen
442869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen        @Override
443869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen        public boolean processMessage(Message message) {
444869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            if (DBG) log(getName() + message.toString());
445869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            switch (message.what) {
446869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                case CMD_USER_WANTS_SIGN_IN:
447869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                    if (message.arg1 != mUserPromptedToken)
448d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen                        return HANDLED;
449a68d21350266a6abc16aeb3fec84eafd85b77cc8Paul Jensen                    transitionTo(mCaptivePortalState);
450d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen                    return HANDLED;
451869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                default:
452869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                    return NOT_HANDLED;
453869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            }
454869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen        }
455869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen
456869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen        @Override
457869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen        public void exit() {
458fdc4e4af7305514359e041dbec742f4b6561e393Paul Jensen            Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0,
459fdc4e4af7305514359e041dbec742f4b6561e393Paul Jensen                    mNetworkAgentInfo.network.netId, null);
460869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            mConnectivityServiceHandler.sendMessage(message);
461869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            mContext.unregisterReceiver(mUserRespondedBroadcastReceiver);
462869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            mUserRespondedBroadcastReceiver = null;
463869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen        }
464869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen    }
465869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen
466ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private class CaptivePortalState extends State {
467869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen        private class CaptivePortalLoggedInBroadcastReceiver extends BroadcastReceiver {
468869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            private final int mToken;
469869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen
470869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            CaptivePortalLoggedInBroadcastReceiver(int token) {
471869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                mToken = token;
472869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            }
473869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen
474869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            @Override
475869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            public void onReceive(Context context, Intent intent) {
476869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                if (Integer.parseInt(intent.getStringExtra(Intent.EXTRA_TEXT)) ==
477869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                        mNetworkAgentInfo.network.netId) {
478869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                    sendMessage(obtainMessage(CMD_CAPTIVE_PORTAL_LOGGED_IN, mToken,
479869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                            Integer.parseInt(intent.getStringExtra(LOGGED_IN_RESULT))));
480869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                }
481869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            }
482869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen        }
483869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen
484869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen        private CaptivePortalLoggedInBroadcastReceiver mCaptivePortalLoggedInBroadcastReceiver;
485869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen
486ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        @Override
487ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        public void enter() {
488869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            Intent intent = new Intent(Intent.ACTION_SEND);
489869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetworkAgentInfo.network.netId));
490869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            intent.setType("text/plain");
491869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            intent.setComponent(new ComponentName("com.android.captiveportallogin",
492869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                    "com.android.captiveportallogin.CaptivePortalLoginActivity"));
493869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
494869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen
495869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            // Wait for result.
496869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            mCaptivePortalLoggedInBroadcastReceiver = new CaptivePortalLoggedInBroadcastReceiver(
497869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                    ++mCaptivePortalLoggedInToken);
498869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            IntentFilter filter = new IntentFilter(ACTION_CAPTIVE_PORTAL_LOGGED_IN);
499869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            mContext.registerReceiver(mCaptivePortalLoggedInBroadcastReceiver, filter);
500869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            // Initiate app to log in.
501869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
502ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        }
503ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
504ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        @Override
505ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        public boolean processMessage(Message message) {
506ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            if (DBG) log(getName() + message.toString());
507ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            switch (message.what) {
508869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                case CMD_CAPTIVE_PORTAL_LOGGED_IN:
509869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                    if (message.arg1 != mCaptivePortalLoggedInToken)
510d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen                        return HANDLED;
511869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                    if (message.arg2 == 0) {
512ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen                        mUserDoesNotWant = true;
513869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                        // TODO: Should teardown network.
514869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                        transitionTo(mOfflineState);
515869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                    } else {
516869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                        transitionTo(mValidatedState);
517869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                    }
518d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen                    return HANDLED;
519ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                default:
520ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    return NOT_HANDLED;
521ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            }
522ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        }
523869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen
524869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen        @Override
525869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen        public void exit() {
526869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            mContext.unregisterReceiver(mCaptivePortalLoggedInBroadcastReceiver);
527869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            mCaptivePortalLoggedInBroadcastReceiver = null;
528869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen        }
529ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    }
530ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
531ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private class LingeringState extends State {
53279a08051c5588d8420656813b21993d490e93dd0Paul Jensen        private static final String ACTION_LINGER_EXPIRED = "android.net.netmon.lingerExpired";
53379a08051c5588d8420656813b21993d490e93dd0Paul Jensen
534dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen        private CustomIntentReceiver mBroadcastReceiver;
53579a08051c5588d8420656813b21993d490e93dd0Paul Jensen        private PendingIntent mIntent;
53679a08051c5588d8420656813b21993d490e93dd0Paul Jensen
537ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        @Override
538ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        public void enter() {
539dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen            mBroadcastReceiver = new CustomIntentReceiver(ACTION_LINGER_EXPIRED, ++mLingerToken,
540dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen                    CMD_LINGER_EXPIRED);
541dcbe8356138bfba3f4bad31c1a7ad036b86f47f4Paul Jensen            mIntent = mBroadcastReceiver.getPendingIntent();
54279a08051c5588d8420656813b21993d490e93dd0Paul Jensen            long wakeupTime = SystemClock.elapsedRealtime() + mLingerDelayMs;
54379a08051c5588d8420656813b21993d490e93dd0Paul Jensen            mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, wakeupTime,
54479a08051c5588d8420656813b21993d490e93dd0Paul Jensen                    // Give a specific window so we aren't subject to unknown inexactitude.
54579a08051c5588d8420656813b21993d490e93dd0Paul Jensen                    mLingerDelayMs / 6, mIntent);
546ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        }
547ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
548ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        @Override
549ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        public boolean processMessage(Message message) {
550ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            if (DBG) log(getName() + message.toString());
551ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            switch (message.what) {
552ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                case CMD_NETWORK_CONNECTED:
553ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    // Go straight to active as we've already evaluated.
554ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    transitionTo(mValidatedState);
555d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen                    return HANDLED;
556ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                case CMD_LINGER_EXPIRED:
557ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    if (message.arg1 != mLingerToken)
558d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen                        return HANDLED;
559ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    mConnectivityServiceHandler.sendMessage(
560ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                            obtainMessage(EVENT_NETWORK_LINGER_COMPLETE, mNetworkAgentInfo));
561d6a3f7ed90d7fa861f579968f12cc30015b3a989Paul Jensen                    return HANDLED;
562ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen                case CMD_FORCE_REEVALUATION:
563ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen                    // Ignore reevaluation attempts when lingering.  A reevaluation could result
564ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen                    // in a transition to the validated state which would abort the linger
565ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen                    // timeout.  Lingering is the result of score assessment; validity is
566ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen                    // irrelevant.
567ad50a1fed01e7c24531ad26d9de70c7b0127ea76Paul Jensen                    return HANDLED;
568ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                default:
569ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                    return NOT_HANDLED;
570ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            }
571ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        }
57279a08051c5588d8420656813b21993d490e93dd0Paul Jensen
57379a08051c5588d8420656813b21993d490e93dd0Paul Jensen        @Override
57479a08051c5588d8420656813b21993d490e93dd0Paul Jensen        public void exit() {
57579a08051c5588d8420656813b21993d490e93dd0Paul Jensen            mAlarmManager.cancel(mIntent);
57679a08051c5588d8420656813b21993d490e93dd0Paul Jensen            mContext.unregisterReceiver(mBroadcastReceiver);
57779a08051c5588d8420656813b21993d490e93dd0Paul Jensen        }
578ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    }
579ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
580ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    /**
581ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * Do a URL fetch on a known server to see if we get the data we expect.
582ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     * Returns HTTP response code.
583ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen     */
584ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    private int isCaptivePortal() {
585ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        if (!mIsCaptivePortalCheckEnabled) return 204;
586ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen
587ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        HttpURLConnection urlConnection = null;
588869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen        int httpResponseCode = 599;
589ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        try {
590e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            URL url = new URL("http", mServer, "/generate_204");
591e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            if (DBG) {
592e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen                log("Checking " + url.toString() + " on " +
593e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen                        mNetworkAgentInfo.networkInfo.getExtraInfo());
594e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            }
5959f1274b7e43d14c7e3a42148ebfda3905fec8b06Lorenzo Colitti            urlConnection = (HttpURLConnection) mNetworkAgentInfo.network.openConnection(url);
596e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            urlConnection.setInstanceFollowRedirects(false);
597e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
598e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
599e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            urlConnection.setUseCaches(false);
600306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen
601306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen            // Time how long it takes to get a response to our request
602306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen            long requestTimestamp = SystemClock.elapsedRealtime();
603306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen
604e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            urlConnection.getInputStream();
605306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen
606306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen            // Time how long it takes to get a response to our request
607306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen            long responseTimestamp = SystemClock.elapsedRealtime();
608306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen
609e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            httpResponseCode = urlConnection.getResponseCode();
610e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            if (DBG) {
611e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen                log("isCaptivePortal: ret=" + httpResponseCode +
612e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen                        " headers=" + urlConnection.getHeaderFields());
613e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            }
614e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive
615e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            // portal.  The only example of this seen so far was a captive portal.  For
616e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            // the time being go with prior behavior of assuming it's not a captive
617e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            // portal.  If it is considered a captive portal, a different sign-in URL
618e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            // is needed (i.e. can't browse a 204).  This could be the result of an HTTP
619e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            // proxy server.
620e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen
621e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            // Consider 200 response with "Content-length=0" to not be a captive portal.
622e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            // There's no point in considering this a captive portal as the user cannot
623e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            // sign-in to an empty page.  Probably the result of a broken transparent proxy.
624e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            // See http://b/9972012.
625e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen            if (httpResponseCode == 200 && urlConnection.getContentLength() == 0) {
626e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen                if (DBG) log("Empty 200 response interpreted as 204 response.");
627e547ff281020b08eb51ef7b2786831f7aacdd73cPaul Jensen                httpResponseCode = 204;
628ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            }
629306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen
630306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen            sendNetworkConditionsBroadcast(true /* response received */, httpResponseCode == 204,
631306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                    requestTimestamp, responseTimestamp);
632ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        } catch (IOException e) {
633ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            if (DBG) log("Probably not a portal: exception " + e);
634869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            if (httpResponseCode == 599) {
635869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen                // TODO: Ping gateway and DNS server and log results.
636869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen            }
637ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        } finally {
638ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            if (urlConnection != null) {
639ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen                urlConnection.disconnect();
640ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen            }
641ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        }
642ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen        return httpResponseCode;
643ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen    }
644306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen
645306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen    /**
646306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen     * @param responseReceived - whether or not we received a valid HTTP response to our request.
647306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen     * If false, isCaptivePortal and responseTimestampMs are ignored
648306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen     * TODO: This should be moved to the transports.  The latency could be passed to the transports
649306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen     * along with the captive portal result.  Currently the TYPE_MOBILE broadcasts appear unused so
650306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen     * perhaps this could just be added to the WiFi transport only.
651306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen     */
652306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen    private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal,
653306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen            long requestTimestampMs, long responseTimestampMs) {
654306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen        if (Settings.Global.getInt(mContext.getContentResolver(),
655306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0) {
656306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen            if (DBG) log("Don't send network conditions - lacking user consent.");
657306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen            return;
658306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen        }
659306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen
660fb68f8fbe0213841f393f8bdb5313e4e44f4f116Robert Greenwalt        if (systemReady == false) return;
661fb68f8fbe0213841f393f8bdb5313e4e44f4f116Robert Greenwalt
662306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen        Intent latencyBroadcast = new Intent(ACTION_NETWORK_CONDITIONS_MEASURED);
663306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen        switch (mNetworkAgentInfo.networkInfo.getType()) {
664306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen            case ConnectivityManager.TYPE_WIFI:
665306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
666306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                if (currentWifiInfo != null) {
667306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                    // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
668306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                    // surrounded by double quotation marks (thus violating the Javadoc), but this
669306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                    // was changed to match the Javadoc in API 17. Since clients may have started
670306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                    // sanitizing the output of this method since API 17 was released, we should
671306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                    // not change it here as it would become impossible to tell whether the SSID is
672306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                    // simply being surrounded by quotes due to the API, or whether those quotes
673306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                    // are actually part of the SSID.
674306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                    latencyBroadcast.putExtra(EXTRA_SSID, currentWifiInfo.getSSID());
675306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                    latencyBroadcast.putExtra(EXTRA_BSSID, currentWifiInfo.getBSSID());
676306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                } else {
677306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                    if (DBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
678306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                    return;
679306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                }
680306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                break;
681306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen            case ConnectivityManager.TYPE_MOBILE:
682306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                latencyBroadcast.putExtra(EXTRA_NETWORK_TYPE, mTelephonyManager.getNetworkType());
683306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                List<CellInfo> info = mTelephonyManager.getAllCellInfo();
684306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                if (info == null) return;
685306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                int numRegisteredCellInfo = 0;
686306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                for (CellInfo cellInfo : info) {
687306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                    if (cellInfo.isRegistered()) {
688306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                        numRegisteredCellInfo++;
689306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                        if (numRegisteredCellInfo > 1) {
690306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                            if (DBG) log("more than one registered CellInfo.  Can't " +
691306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                                    "tell which is active.  Bailing.");
692306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                            return;
693306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                        }
694306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                        if (cellInfo instanceof CellInfoCdma) {
695306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                            CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity();
696306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                            latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
697306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                        } else if (cellInfo instanceof CellInfoGsm) {
698306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                            CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity();
699306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                            latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
700306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                        } else if (cellInfo instanceof CellInfoLte) {
701306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                            CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity();
702306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                            latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
703306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                        } else if (cellInfo instanceof CellInfoWcdma) {
704306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                            CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity();
705306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                            latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
706306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                        } else {
707306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                            if (DBG) logw("Registered cellinfo is unrecognized");
708306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                            return;
709306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                        }
710306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                    }
711306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                }
712306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                break;
713306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen            default:
714306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen                return;
715306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen        }
716306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen        latencyBroadcast.putExtra(EXTRA_CONNECTIVITY_TYPE, mNetworkAgentInfo.networkInfo.getType());
717306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen        latencyBroadcast.putExtra(EXTRA_RESPONSE_RECEIVED, responseReceived);
718306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen        latencyBroadcast.putExtra(EXTRA_REQUEST_TIMESTAMP_MS, requestTimestampMs);
719306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen
720306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen        if (responseReceived) {
721306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen            latencyBroadcast.putExtra(EXTRA_IS_CAPTIVE_PORTAL, isCaptivePortal);
722306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen            latencyBroadcast.putExtra(EXTRA_RESPONSE_TIMESTAMP_MS, responseTimestampMs);
723306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen        }
72455298581a0750505bc7da0539fe94255f95326a4Paul Jensen        mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT,
72555298581a0750505bc7da0539fe94255f95326a4Paul Jensen                PERMISSION_ACCESS_NETWORK_CONDITIONS);
726306f1a45c636e3721ae4b84b8797e6349ae6ff57Paul Jensen    }
727ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7Paul Jensen}
728