1616
1717package android .net ;
1818
19+ import android .app .Activity ;
1920import android .app .Notification ;
2021import android .app .NotificationManager ;
2122import android .app .PendingIntent ;
2425import android .content .Intent ;
2526import android .content .IntentFilter ;
2627import android .content .res .Resources ;
27- import android .database .ContentObserver ;
2828import android .net .ConnectivityManager ;
2929import android .net .IConnectivityManager ;
30- import android .os .Handler ;
31- import android .os .HandlerThread ;
32- import android .os .Looper ;
30+ import android .os .UserHandle ;
3331import android .os .Message ;
3432import android .os .RemoteException ;
3533import android .provider .Settings ;
3634import android .util .Log ;
3735
36+ import com .android .internal .util .State ;
37+ import com .android .internal .util .StateMachine ;
38+
3839import java .io .IOException ;
3940import java .net .HttpURLConnection ;
4041import java .net .InetAddress ;
4142import java .net .Inet4Address ;
4243import java .net .URL ;
4344import java .net .UnknownHostException ;
44- import java .util .concurrent .atomic .AtomicBoolean ;
4545
4646import 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