Skip to content

Commit 314488b

Browse files
isheriffAndroid (Google) Code Review
authored andcommitted
Merge "Captive check for both mobile and wifi" into jb-mr1-dev
2 parents d86077c + 9538bdd commit 314488b

File tree

2 files changed

+170
-89
lines changed

2 files changed

+170
-89
lines changed

core/java/android/net/CaptivePortalTracker.java

Lines changed: 164 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package android.net;
1818

19+
import android.app.Activity;
1920
import android.app.Notification;
2021
import android.app.NotificationManager;
2122
import android.app.PendingIntent;
@@ -24,33 +25,32 @@
2425
import android.content.Intent;
2526
import android.content.IntentFilter;
2627
import android.content.res.Resources;
27-
import android.database.ContentObserver;
2828
import android.net.ConnectivityManager;
2929
import android.net.IConnectivityManager;
30-
import android.os.Handler;
31-
import android.os.HandlerThread;
32-
import android.os.Looper;
30+
import android.os.UserHandle;
3331
import android.os.Message;
3432
import android.os.RemoteException;
3533
import android.provider.Settings;
3634
import android.util.Log;
3735

36+
import com.android.internal.util.State;
37+
import com.android.internal.util.StateMachine;
38+
3839
import java.io.IOException;
3940
import java.net.HttpURLConnection;
4041
import java.net.InetAddress;
4142
import java.net.Inet4Address;
4243
import java.net.URL;
4344
import java.net.UnknownHostException;
44-
import java.util.concurrent.atomic.AtomicBoolean;
4545

4646
import com.android.internal.R;
4747

4848
/**
49-
* This class allows captive portal detection
49+
* This class allows captive portal detection on a network.
5050
* @hide
5151
*/
52-
public class CaptivePortalTracker {
53-
private static final boolean DBG = true;
52+
public class CaptivePortalTracker extends StateMachine {
53+
private static final boolean DBG = false;
5454
private static final String TAG = "CaptivePortalTracker";
5555

5656
private static final String DEFAULT_SERVER = "clients3.google.com";
@@ -62,37 +62,31 @@ public class CaptivePortalTracker {
6262
private String mUrl;
6363
private boolean mNotificationShown = false;
6464
private boolean mIsCaptivePortalCheckEnabled = false;
65-
private InternalHandler mHandler;
6665
private IConnectivityManager mConnService;
6766
private Context mContext;
6867
private NetworkInfo mNetworkInfo;
69-
private boolean mIsCaptivePortal = false;
7068

71-
private static final int DETECT_PORTAL = 0;
72-
private static final int HANDLE_CONNECT = 1;
69+
private static final int CMD_DETECT_PORTAL = 0;
70+
private static final int CMD_CONNECTIVITY_CHANGE = 1;
71+
private static final int CMD_DELAYED_CAPTIVE_CHECK = 2;
7372

74-
/**
75-
* Activity Action: Switch to the captive portal network
76-
* <p>Input: Nothing.
77-
* <p>Output: Nothing.
78-
*/
79-
public static final String ACTION_SWITCH_TO_CAPTIVE_PORTAL
80-
= "android.net.SWITCH_TO_CAPTIVE_PORTAL";
73+
/* This delay happens every time before we do a captive check on a network */
74+
private static final int DELAYED_CHECK_INTERVAL_MS = 10000;
75+
private int mDelayedCheckToken = 0;
76+
77+
private State mDefaultState = new DefaultState();
78+
private State mNoActiveNetworkState = new NoActiveNetworkState();
79+
private State mActiveNetworkState = new ActiveNetworkState();
80+
private State mDelayedCaptiveCheckState = new DelayedCaptiveCheckState();
81+
82+
private CaptivePortalTracker(Context context, IConnectivityManager cs) {
83+
super(TAG);
8184

82-
private CaptivePortalTracker(Context context, NetworkInfo info, IConnectivityManager cs) {
8385
mContext = context;
84-
mNetworkInfo = info;
8586
mConnService = cs;
8687

87-
HandlerThread handlerThread = new HandlerThread("CaptivePortalThread");
88-
handlerThread.start();
89-
mHandler = new InternalHandler(handlerThread.getLooper());
90-
mHandler.obtainMessage(DETECT_PORTAL).sendToTarget();
91-
9288
IntentFilter filter = new IntentFilter();
93-
filter.addAction(ACTION_SWITCH_TO_CAPTIVE_PORTAL);
9489
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
95-
9690
mContext.registerReceiver(mReceiver, filter);
9791

9892
mServer = Settings.Secure.getString(mContext.getContentResolver(),
@@ -101,100 +95,180 @@ private CaptivePortalTracker(Context context, NetworkInfo info, IConnectivityMan
10195

10296
mIsCaptivePortalCheckEnabled = Settings.Secure.getInt(mContext.getContentResolver(),
10397
Settings.Secure.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;
98+
99+
addState(mDefaultState);
100+
addState(mNoActiveNetworkState, mDefaultState);
101+
addState(mActiveNetworkState, mDefaultState);
102+
addState(mDelayedCaptiveCheckState, mActiveNetworkState);
103+
setInitialState(mNoActiveNetworkState);
104104
}
105105

106106
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
107107
@Override
108108
public void onReceive(Context context, Intent intent) {
109109
String action = intent.getAction();
110-
if (action.equals(ACTION_SWITCH_TO_CAPTIVE_PORTAL)) {
111-
notifyPortalCheckComplete();
112-
} else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
110+
if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
113111
NetworkInfo info = intent.getParcelableExtra(
114112
ConnectivityManager.EXTRA_NETWORK_INFO);
115-
mHandler.obtainMessage(HANDLE_CONNECT, info).sendToTarget();
113+
sendMessage(obtainMessage(CMD_CONNECTIVITY_CHANGE, info));
116114
}
117115
}
118116
};
119117

120-
public static CaptivePortalTracker detect(Context context, NetworkInfo info,
118+
public static CaptivePortalTracker makeCaptivePortalTracker(Context context,
121119
IConnectivityManager cs) {
122-
CaptivePortalTracker captivePortal = new CaptivePortalTracker(context, info, cs);
120+
CaptivePortalTracker captivePortal = new CaptivePortalTracker(context, cs);
121+
captivePortal.start();
123122
return captivePortal;
124123
}
125124

126-
private class InternalHandler extends Handler {
127-
public InternalHandler(Looper looper) {
128-
super(looper);
125+
public void detectCaptivePortal(NetworkInfo info) {
126+
sendMessage(obtainMessage(CMD_DETECT_PORTAL, info));
127+
}
128+
129+
private class DefaultState extends State {
130+
@Override
131+
public void enter() {
132+
if (DBG) log(getName() + "\n");
129133
}
130134

131135
@Override
132-
public void handleMessage(Message msg) {
133-
switch (msg.what) {
134-
case DETECT_PORTAL:
135-
InetAddress server = lookupHost(mServer);
136-
if (server != null) {
137-
requestRouteToHost(server);
138-
if (isCaptivePortal(server)) {
139-
if (DBG) log("Captive portal " + mNetworkInfo);
140-
setNotificationVisible(true);
141-
mIsCaptivePortal = true;
142-
break;
143-
}
144-
}
145-
notifyPortalCheckComplete();
146-
quit();
136+
public boolean processMessage(Message message) {
137+
if (DBG) log(getName() + message.toString() + "\n");
138+
switch (message.what) {
139+
case CMD_DETECT_PORTAL:
140+
NetworkInfo info = (NetworkInfo) message.obj;
141+
// Checking on a secondary connection is not supported
142+
// yet
143+
notifyPortalCheckComplete(info);
147144
break;
148-
case HANDLE_CONNECT:
149-
NetworkInfo info = (NetworkInfo) msg.obj;
150-
if (info.getType() != mNetworkInfo.getType()) break;
145+
case CMD_CONNECTIVITY_CHANGE:
146+
case CMD_DELAYED_CAPTIVE_CHECK:
147+
break;
148+
default:
149+
loge("Ignoring " + message);
150+
break;
151+
}
152+
return HANDLED;
153+
}
154+
}
151155

152-
if (info.getState() == NetworkInfo.State.CONNECTED ||
153-
info.getState() == NetworkInfo.State.DISCONNECTED) {
154-
setNotificationVisible(false);
155-
}
156+
private class NoActiveNetworkState extends State {
157+
@Override
158+
public void enter() {
159+
if (DBG) log(getName() + "\n");
160+
mNetworkInfo = null;
161+
/* Clear any previous notification */
162+
setNotificationVisible(false);
163+
}
156164

157-
/* Connected to a captive portal */
158-
if (info.getState() == NetworkInfo.State.CONNECTED &&
159-
mIsCaptivePortal) {
160-
launchBrowser();
161-
quit();
165+
@Override
166+
public boolean processMessage(Message message) {
167+
if (DBG) log(getName() + message.toString() + "\n");
168+
InetAddress server;
169+
NetworkInfo info;
170+
switch (message.what) {
171+
case CMD_CONNECTIVITY_CHANGE:
172+
info = (NetworkInfo) message.obj;
173+
if (info.isConnected() && isActiveNetwork(info)) {
174+
mNetworkInfo = info;
175+
transitionTo(mDelayedCaptiveCheckState);
162176
}
163177
break;
164178
default:
165-
loge("Unhandled message " + msg);
166-
break;
179+
return NOT_HANDLED;
167180
}
181+
return HANDLED;
182+
}
183+
}
184+
185+
private class ActiveNetworkState extends State {
186+
@Override
187+
public void enter() {
188+
if (DBG) log(getName() + "\n");
168189
}
169190

170-
private void quit() {
171-
mIsCaptivePortal = false;
172-
getLooper().quit();
173-
mContext.unregisterReceiver(mReceiver);
191+
@Override
192+
public boolean processMessage(Message message) {
193+
NetworkInfo info;
194+
switch (message.what) {
195+
case CMD_CONNECTIVITY_CHANGE:
196+
info = (NetworkInfo) message.obj;
197+
if (!info.isConnected()
198+
&& info.getType() == mNetworkInfo.getType()) {
199+
if (DBG) log("Disconnected from active network " + info);
200+
transitionTo(mNoActiveNetworkState);
201+
} else if (info.getType() != mNetworkInfo.getType() &&
202+
info.isConnected() &&
203+
isActiveNetwork(info)) {
204+
if (DBG) log("Active network switched " + info);
205+
deferMessage(message);
206+
transitionTo(mNoActiveNetworkState);
207+
}
208+
break;
209+
default:
210+
return NOT_HANDLED;
211+
}
212+
return HANDLED;
174213
}
175214
}
176215

177-
private void launchBrowser() {
178-
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mUrl));
179-
intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
180-
mContext.startActivity(intent);
216+
217+
218+
private class DelayedCaptiveCheckState extends State {
219+
@Override
220+
public void enter() {
221+
if (DBG) log(getName() + "\n");
222+
sendMessageDelayed(obtainMessage(CMD_DELAYED_CAPTIVE_CHECK,
223+
++mDelayedCheckToken, 0), DELAYED_CHECK_INTERVAL_MS);
224+
}
225+
226+
@Override
227+
public boolean processMessage(Message message) {
228+
if (DBG) log(getName() + message.toString() + "\n");
229+
switch (message.what) {
230+
case CMD_DELAYED_CAPTIVE_CHECK:
231+
if (message.arg1 == mDelayedCheckToken) {
232+
InetAddress server = lookupHost(mServer);
233+
if (server != null) {
234+
if (isCaptivePortal(server)) {
235+
if (DBG) log("Captive network " + mNetworkInfo);
236+
setNotificationVisible(true);
237+
}
238+
}
239+
if (DBG) log("Not captive network " + mNetworkInfo);
240+
transitionTo(mActiveNetworkState);
241+
}
242+
break;
243+
default:
244+
return NOT_HANDLED;
245+
}
246+
return HANDLED;
247+
}
181248
}
182249

183-
private void notifyPortalCheckComplete() {
250+
private void notifyPortalCheckComplete(NetworkInfo info) {
251+
if (info == null) {
252+
loge("notifyPortalCheckComplete on null");
253+
return;
254+
}
184255
try {
185-
mConnService.captivePortalCheckComplete(mNetworkInfo);
256+
mConnService.captivePortalCheckComplete(info);
186257
} catch(RemoteException e) {
187258
e.printStackTrace();
188259
}
189260
}
190261

191-
private void requestRouteToHost(InetAddress server) {
262+
private boolean isActiveNetwork(NetworkInfo info) {
192263
try {
193-
mConnService.requestRouteToHostAddress(mNetworkInfo.getType(),
194-
server.getAddress());
264+
NetworkInfo active = mConnService.getActiveNetworkInfo();
265+
if (active != null && active.getType() == info.getType()) {
266+
return true;
267+
}
195268
} catch (RemoteException e) {
196269
e.printStackTrace();
197270
}
271+
return false;
198272
}
199273

200274
/**
@@ -205,6 +279,7 @@ private boolean isCaptivePortal(InetAddress server) {
205279
if (!mIsCaptivePortalCheckEnabled) return false;
206280

207281
mUrl = "http://" + server.getHostAddress() + "/generate_204";
282+
if (DBG) log("Checking " + mUrl);
208283
try {
209284
URL url = new URL(mUrl);
210285
urlConnection = (HttpURLConnection) url.openConnection();
@@ -250,17 +325,23 @@ private void setNotificationVisible(boolean visible) {
250325
.getSystemService(Context.NOTIFICATION_SERVICE);
251326

252327
if (visible) {
253-
CharSequence title = r.getString(R.string.wifi_available_sign_in, 0);
328+
CharSequence title;
329+
if (mNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
330+
title = r.getString(R.string.wifi_available_sign_in, 0);
331+
} else {
332+
title = r.getString(R.string.network_available_sign_in, 0);
333+
}
254334
CharSequence details = r.getString(R.string.network_available_sign_in_detailed,
255335
mNetworkInfo.getExtraInfo());
256336

257337
Notification notification = new Notification();
258338
notification.when = 0;
259339
notification.icon = com.android.internal.R.drawable.stat_notify_wifi_in_range;
260340
notification.flags = Notification.FLAG_AUTO_CANCEL;
261-
notification.contentIntent = PendingIntent.getBroadcast(mContext, 0,
262-
new Intent(CaptivePortalTracker.ACTION_SWITCH_TO_CAPTIVE_PORTAL), 0);
263-
341+
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mUrl));
342+
intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
343+
Intent.FLAG_ACTIVITY_NEW_TASK);
344+
notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
264345
notification.tickerText = title;
265346
notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
266347

0 commit comments

Comments
 (0)