diff -r 000000000000 -r 6474c204b198 mobile/android/base/GeckoNetworkManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mobile/android/base/GeckoNetworkManager.java Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,268 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko; + +import org.mozilla.gecko.mozglue.JNITarget; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.DhcpInfo; +import android.net.NetworkInfo; +import android.net.wifi.WifiManager; +import android.telephony.TelephonyManager; +import android.util.Log; + +/* + * A part of the work of GeckoNetworkManager is to give an general connection + * type based on the current connection. According to spec of NetworkInformation + * API version 3, connection types include: bluetooth, cellular, ethernet, none, + * wifi and other. The objective of providing such general connection is due to + * some security concerns. In short, we don't want to expose the information of + * exact network type, especially the cellular network type. + * + * Current connection is firstly obtained from Android's ConnectivityManager, + * which is represented by the constant, and then will be mapped into the + * connection type defined in Network Information API version 3. + */ + +public class GeckoNetworkManager extends BroadcastReceiver { + private static final String LOGTAG = "GeckoNetworkManager"; + + static private final GeckoNetworkManager sInstance = new GeckoNetworkManager(); + + // Connection Type defined in Network Information API v3. + private enum ConnectionType { + CELLULAR(0), + BLUETOOTH(1), + ETHERNET(2), + WIFI(3), + OTHER(4), + NONE(5); + + public final int value; + + private ConnectionType(int value) { + this.value = value; + } + } + + private enum InfoType { + MCC, + MNC + } + + private ConnectionType mConnectionType = ConnectionType.NONE; + private final IntentFilter mNetworkFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); + + // Whether the manager should be listening to Network Information changes. + private boolean mShouldBeListening = false; + + // Whether the manager should notify Gecko that a change in Network + // Information happened. + private boolean mShouldNotify = false; + + // The application context used for registering receivers, so + // we can unregister them again later. + private volatile Context mApplicationContext; + + public static GeckoNetworkManager getInstance() { + return sInstance; + } + + @Override + public void onReceive(Context aContext, Intent aIntent) { + updateConnectionType(); + } + + public void start(final Context context) { + // Note that this initialization clause only runs once. + mApplicationContext = context.getApplicationContext(); + if (mConnectionType == ConnectionType.NONE) { + mConnectionType = getConnectionType(); + } + + mShouldBeListening = true; + updateConnectionType(); + + if (mShouldNotify) { + startListening(); + } + } + + private void startListening() { + final Context appContext = mApplicationContext; + if (appContext == null) { + Log.w(LOGTAG, "Not registering receiver: no context!"); + return; + } + + Log.v(LOGTAG, "Registering receiver."); + appContext.registerReceiver(this, mNetworkFilter); + } + + public void stop() { + mShouldBeListening = false; + + if (mShouldNotify) { + stopListening(); + } + } + + private void stopListening() { + if (null == mApplicationContext) { + return; + } + + mApplicationContext.unregisterReceiver(this); + } + + private int wifiDhcpGatewayAddress() { + if (mConnectionType != ConnectionType.WIFI) { + return 0; + } + + if (null == mApplicationContext) { + return 0; + } + + try { + WifiManager mgr = (WifiManager) mApplicationContext.getSystemService(Context.WIFI_SERVICE); + DhcpInfo d = mgr.getDhcpInfo(); + if (d == null) { + return 0; + } + + return d.gateway; + + } catch (Exception ex) { + // getDhcpInfo() is not documented to require any permissions, but on some devices + // requires android.permission.ACCESS_WIFI_STATE. Just catch the generic exception + // here and returning 0. Not logging because this could be noisy. + return 0; + } + } + + private void updateConnectionType() { + ConnectionType previousConnectionType = mConnectionType; + mConnectionType = getConnectionType(); + + if (mConnectionType == previousConnectionType || !mShouldNotify) { + return; + } + + GeckoAppShell.sendEventToGecko(GeckoEvent.createNetworkEvent( + mConnectionType.value, + mConnectionType == ConnectionType.WIFI, + wifiDhcpGatewayAddress())); + } + + public double[] getCurrentInformation() { + return new double[] { mConnectionType.value, + (mConnectionType == ConnectionType.WIFI) ? 1.0 : 0.0, + wifiDhcpGatewayAddress()}; + } + + public void enableNotifications() { + // We set mShouldNotify *after* calling updateConnectionType() to make sure we + // don't notify an eventual change in mConnectionType. + mConnectionType = ConnectionType.NONE; // force a notification + updateConnectionType(); + mShouldNotify = true; + + if (mShouldBeListening) { + startListening(); + } + } + + public void disableNotifications() { + mShouldNotify = false; + + if (mShouldBeListening) { + stopListening(); + } + } + + private ConnectionType getConnectionType() { + final Context appContext = mApplicationContext; + + if (null == appContext) { + return ConnectionType.NONE; + } + + ConnectivityManager cm = (ConnectivityManager) appContext.getSystemService(Context.CONNECTIVITY_SERVICE); + if (cm == null) { + Log.e(LOGTAG, "Connectivity service does not exist"); + return ConnectionType.NONE; + } + + NetworkInfo ni = null; + try { + ni = cm.getActiveNetworkInfo(); + } catch (SecurityException se) {} // if we don't have the permission, fall through to null check + + if (ni == null) { + return ConnectionType.NONE; + } + + switch (ni.getType()) { + case ConnectivityManager.TYPE_BLUETOOTH: + return ConnectionType.BLUETOOTH; + case ConnectivityManager.TYPE_ETHERNET: + return ConnectionType.ETHERNET; + case ConnectivityManager.TYPE_MOBILE: + case ConnectivityManager.TYPE_WIMAX: + return ConnectionType.CELLULAR; + case ConnectivityManager.TYPE_WIFI: + return ConnectionType.WIFI; + default: + Log.w(LOGTAG, "Ignoring the current network type."); + return ConnectionType.OTHER; + } + } + + private static int getNetworkOperator(InfoType type, Context context) { + if (null == context) { + return -1; + } + + TelephonyManager tel = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + if (tel == null) { + Log.e(LOGTAG, "Telephony service does not exist"); + return -1; + } + + String networkOperator = tel.getNetworkOperator(); + if (networkOperator == null || networkOperator.length() <= 3) { + return -1; + } + if (type == InfoType.MNC) { + return Integer.parseInt(networkOperator.substring(3)); + } else if (type == InfoType.MCC) { + return Integer.parseInt(networkOperator.substring(0, 3)); + } + + return -1; + } + + /** + * These are called from JavaScript ctypes. Avoid letting ProGuard delete them. + * + * Note that these methods must only be called after GeckoAppShell has been + * initialized: they depend on access to the context. + */ + @JNITarget + public static int getMCC() { + return getNetworkOperator(InfoType.MCC, GeckoAppShell.getContext().getApplicationContext()); + } + + @JNITarget + public static int getMNC() { + return getNetworkOperator(InfoType.MNC, GeckoAppShell.getContext().getApplicationContext()); + } +}