PageRenderTime 90ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/core/java/android/net/CaptivePortalTracker.java

https://bitbucket.org/ardalanaz/platform_frameworks_base
Java | 382 lines | 307 code | 45 blank | 30 comment | 51 complexity | 88cec8559a9c9abfdd260413c27eb91d MD5 | raw file
  1. /*
  2. * Copyright (C) 2012 The Android Open Source Project
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package android.net;
  17. import android.app.Activity;
  18. import android.app.Notification;
  19. import android.app.NotificationManager;
  20. import android.app.PendingIntent;
  21. import android.content.BroadcastReceiver;
  22. import android.content.Context;
  23. import android.content.Intent;
  24. import android.content.IntentFilter;
  25. import android.content.res.Resources;
  26. import android.net.ConnectivityManager;
  27. import android.net.IConnectivityManager;
  28. import android.os.UserHandle;
  29. import android.os.Message;
  30. import android.os.RemoteException;
  31. import android.provider.Settings;
  32. import android.telephony.TelephonyManager;
  33. import android.util.Log;
  34. import com.android.internal.util.State;
  35. import com.android.internal.util.StateMachine;
  36. import java.io.IOException;
  37. import java.net.HttpURLConnection;
  38. import java.net.InetAddress;
  39. import java.net.Inet4Address;
  40. import java.net.URL;
  41. import java.net.UnknownHostException;
  42. import com.android.internal.R;
  43. /**
  44. * This class allows captive portal detection on a network.
  45. * @hide
  46. */
  47. public class CaptivePortalTracker extends StateMachine {
  48. private static final boolean DBG = false;
  49. private static final String TAG = "CaptivePortalTracker";
  50. private static final String DEFAULT_SERVER = "clients3.google.com";
  51. private static final String NOTIFICATION_ID = "CaptivePortal.Notification";
  52. private static final int SOCKET_TIMEOUT_MS = 10000;
  53. private String mServer;
  54. private String mUrl;
  55. private boolean mNotificationShown = false;
  56. private boolean mIsCaptivePortalCheckEnabled = false;
  57. private IConnectivityManager mConnService;
  58. private TelephonyManager mTelephonyManager;
  59. private Context mContext;
  60. private NetworkInfo mNetworkInfo;
  61. private static final int CMD_DETECT_PORTAL = 0;
  62. private static final int CMD_CONNECTIVITY_CHANGE = 1;
  63. private static final int CMD_DELAYED_CAPTIVE_CHECK = 2;
  64. /* This delay happens every time before we do a captive check on a network */
  65. private static final int DELAYED_CHECK_INTERVAL_MS = 10000;
  66. private int mDelayedCheckToken = 0;
  67. private State mDefaultState = new DefaultState();
  68. private State mNoActiveNetworkState = new NoActiveNetworkState();
  69. private State mActiveNetworkState = new ActiveNetworkState();
  70. private State mDelayedCaptiveCheckState = new DelayedCaptiveCheckState();
  71. private CaptivePortalTracker(Context context, IConnectivityManager cs) {
  72. super(TAG);
  73. mContext = context;
  74. mConnService = cs;
  75. mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
  76. IntentFilter filter = new IntentFilter();
  77. filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
  78. mContext.registerReceiver(mReceiver, filter);
  79. mServer = Settings.Global.getString(mContext.getContentResolver(),
  80. Settings.Global.CAPTIVE_PORTAL_SERVER);
  81. if (mServer == null) mServer = DEFAULT_SERVER;
  82. mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),
  83. Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;
  84. addState(mDefaultState);
  85. addState(mNoActiveNetworkState, mDefaultState);
  86. addState(mActiveNetworkState, mDefaultState);
  87. addState(mDelayedCaptiveCheckState, mActiveNetworkState);
  88. setInitialState(mNoActiveNetworkState);
  89. }
  90. private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
  91. @Override
  92. public void onReceive(Context context, Intent intent) {
  93. String action = intent.getAction();
  94. if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
  95. NetworkInfo info = intent.getParcelableExtra(
  96. ConnectivityManager.EXTRA_NETWORK_INFO);
  97. sendMessage(obtainMessage(CMD_CONNECTIVITY_CHANGE, info));
  98. }
  99. }
  100. };
  101. public static CaptivePortalTracker makeCaptivePortalTracker(Context context,
  102. IConnectivityManager cs) {
  103. CaptivePortalTracker captivePortal = new CaptivePortalTracker(context, cs);
  104. captivePortal.start();
  105. return captivePortal;
  106. }
  107. public void detectCaptivePortal(NetworkInfo info) {
  108. sendMessage(obtainMessage(CMD_DETECT_PORTAL, info));
  109. }
  110. private class DefaultState extends State {
  111. @Override
  112. public void enter() {
  113. if (DBG) log(getName() + "\n");
  114. }
  115. @Override
  116. public boolean processMessage(Message message) {
  117. if (DBG) log(getName() + message.toString() + "\n");
  118. switch (message.what) {
  119. case CMD_DETECT_PORTAL:
  120. NetworkInfo info = (NetworkInfo) message.obj;
  121. // Checking on a secondary connection is not supported
  122. // yet
  123. notifyPortalCheckComplete(info);
  124. break;
  125. case CMD_CONNECTIVITY_CHANGE:
  126. case CMD_DELAYED_CAPTIVE_CHECK:
  127. break;
  128. default:
  129. loge("Ignoring " + message);
  130. break;
  131. }
  132. return HANDLED;
  133. }
  134. }
  135. private class NoActiveNetworkState extends State {
  136. @Override
  137. public void enter() {
  138. if (DBG) log(getName() + "\n");
  139. mNetworkInfo = null;
  140. /* Clear any previous notification */
  141. setNotificationVisible(false);
  142. }
  143. @Override
  144. public boolean processMessage(Message message) {
  145. if (DBG) log(getName() + message.toString() + "\n");
  146. InetAddress server;
  147. NetworkInfo info;
  148. switch (message.what) {
  149. case CMD_CONNECTIVITY_CHANGE:
  150. info = (NetworkInfo) message.obj;
  151. if (info.isConnected() && isActiveNetwork(info)) {
  152. mNetworkInfo = info;
  153. transitionTo(mDelayedCaptiveCheckState);
  154. }
  155. break;
  156. default:
  157. return NOT_HANDLED;
  158. }
  159. return HANDLED;
  160. }
  161. }
  162. private class ActiveNetworkState extends State {
  163. @Override
  164. public void enter() {
  165. if (DBG) log(getName() + "\n");
  166. }
  167. @Override
  168. public boolean processMessage(Message message) {
  169. NetworkInfo info;
  170. switch (message.what) {
  171. case CMD_CONNECTIVITY_CHANGE:
  172. info = (NetworkInfo) message.obj;
  173. if (!info.isConnected()
  174. && info.getType() == mNetworkInfo.getType()) {
  175. if (DBG) log("Disconnected from active network " + info);
  176. transitionTo(mNoActiveNetworkState);
  177. } else if (info.getType() != mNetworkInfo.getType() &&
  178. info.isConnected() &&
  179. isActiveNetwork(info)) {
  180. if (DBG) log("Active network switched " + info);
  181. deferMessage(message);
  182. transitionTo(mNoActiveNetworkState);
  183. }
  184. break;
  185. default:
  186. return NOT_HANDLED;
  187. }
  188. return HANDLED;
  189. }
  190. }
  191. private class DelayedCaptiveCheckState extends State {
  192. @Override
  193. public void enter() {
  194. if (DBG) log(getName() + "\n");
  195. sendMessageDelayed(obtainMessage(CMD_DELAYED_CAPTIVE_CHECK,
  196. ++mDelayedCheckToken, 0), DELAYED_CHECK_INTERVAL_MS);
  197. }
  198. @Override
  199. public boolean processMessage(Message message) {
  200. if (DBG) log(getName() + message.toString() + "\n");
  201. switch (message.what) {
  202. case CMD_DELAYED_CAPTIVE_CHECK:
  203. if (message.arg1 == mDelayedCheckToken) {
  204. InetAddress server = lookupHost(mServer);
  205. if (server != null) {
  206. if (isCaptivePortal(server)) {
  207. if (DBG) log("Captive network " + mNetworkInfo);
  208. setNotificationVisible(true);
  209. }
  210. }
  211. if (DBG) log("Not captive network " + mNetworkInfo);
  212. transitionTo(mActiveNetworkState);
  213. }
  214. break;
  215. default:
  216. return NOT_HANDLED;
  217. }
  218. return HANDLED;
  219. }
  220. }
  221. private void notifyPortalCheckComplete(NetworkInfo info) {
  222. if (info == null) {
  223. loge("notifyPortalCheckComplete on null");
  224. return;
  225. }
  226. try {
  227. mConnService.captivePortalCheckComplete(info);
  228. } catch(RemoteException e) {
  229. e.printStackTrace();
  230. }
  231. }
  232. private boolean isActiveNetwork(NetworkInfo info) {
  233. try {
  234. NetworkInfo active = mConnService.getActiveNetworkInfo();
  235. if (active != null && active.getType() == info.getType()) {
  236. return true;
  237. }
  238. } catch (RemoteException e) {
  239. e.printStackTrace();
  240. }
  241. return false;
  242. }
  243. /**
  244. * Do a URL fetch on a known server to see if we get the data we expect
  245. */
  246. private boolean isCaptivePortal(InetAddress server) {
  247. HttpURLConnection urlConnection = null;
  248. if (!mIsCaptivePortalCheckEnabled) return false;
  249. mUrl = "http://" + server.getHostAddress() + "/generate_204";
  250. if (DBG) log("Checking " + mUrl);
  251. try {
  252. URL url = new URL(mUrl);
  253. urlConnection = (HttpURLConnection) url.openConnection();
  254. urlConnection.setInstanceFollowRedirects(false);
  255. urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
  256. urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
  257. urlConnection.setUseCaches(false);
  258. urlConnection.getInputStream();
  259. // we got a valid response, but not from the real google
  260. return urlConnection.getResponseCode() != 204;
  261. } catch (IOException e) {
  262. if (DBG) log("Probably not a portal: exception " + e);
  263. return false;
  264. } finally {
  265. if (urlConnection != null) {
  266. urlConnection.disconnect();
  267. }
  268. }
  269. }
  270. private InetAddress lookupHost(String hostname) {
  271. InetAddress inetAddress[];
  272. try {
  273. inetAddress = InetAddress.getAllByName(hostname);
  274. } catch (UnknownHostException e) {
  275. return null;
  276. }
  277. for (InetAddress a : inetAddress) {
  278. if (a instanceof Inet4Address) return a;
  279. }
  280. return null;
  281. }
  282. private void setNotificationVisible(boolean visible) {
  283. // if it should be hidden and it is already hidden, then noop
  284. if (!visible && !mNotificationShown) {
  285. return;
  286. }
  287. Resources r = Resources.getSystem();
  288. NotificationManager notificationManager = (NotificationManager) mContext
  289. .getSystemService(Context.NOTIFICATION_SERVICE);
  290. if (visible) {
  291. CharSequence title;
  292. CharSequence details;
  293. int icon;
  294. switch (mNetworkInfo.getType()) {
  295. case ConnectivityManager.TYPE_WIFI:
  296. title = r.getString(R.string.wifi_available_sign_in, 0);
  297. details = r.getString(R.string.network_available_sign_in_detailed,
  298. mNetworkInfo.getExtraInfo());
  299. icon = R.drawable.stat_notify_wifi_in_range;
  300. break;
  301. case ConnectivityManager.TYPE_MOBILE:
  302. title = r.getString(R.string.network_available_sign_in, 0);
  303. // TODO: Change this to pull from NetworkInfo once a printable
  304. // name has been added to it
  305. details = mTelephonyManager.getNetworkOperatorName();
  306. icon = R.drawable.stat_notify_rssi_in_range;
  307. break;
  308. default:
  309. title = r.getString(R.string.network_available_sign_in, 0);
  310. details = r.getString(R.string.network_available_sign_in_detailed,
  311. mNetworkInfo.getExtraInfo());
  312. icon = R.drawable.stat_notify_rssi_in_range;
  313. break;
  314. }
  315. Notification notification = new Notification();
  316. notification.when = 0;
  317. notification.icon = icon;
  318. notification.flags = Notification.FLAG_AUTO_CANCEL;
  319. Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mUrl));
  320. intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
  321. Intent.FLAG_ACTIVITY_NEW_TASK);
  322. notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
  323. notification.tickerText = title;
  324. notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
  325. notificationManager.notify(NOTIFICATION_ID, 1, notification);
  326. } else {
  327. notificationManager.cancel(NOTIFICATION_ID, 1);
  328. }
  329. mNotificationShown = visible;
  330. }
  331. private static void log(String s) {
  332. Log.d(TAG, s);
  333. }
  334. private static void loge(String s) {
  335. Log.e(TAG, s);
  336. }
  337. }