/* * PhoneGap is available under *either* the terms of the modified BSD license *or* the * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. * * Copyright (c) 2005-2010, Nitobi Software Inc. * Copyright (c) 2010, IBM Corporation */ package com.phonegap.network; import java.util.Enumeration; import java.util.Hashtable; import com.phonegap.PhoneGapExtension; import com.phonegap.api.PluginResult; import com.phonegap.json4j.JSONException; import com.phonegap.json4j.JSONObject; import com.phonegap.util.Logger; import net.rim.device.api.system.Application; import net.rim.device.api.system.CoverageInfo; import net.rim.device.api.system.CoverageStatusListener; import net.rim.device.api.system.RadioInfo; import net.rim.device.api.system.RadioStatusListener; import net.rim.device.api.system.WLANConnectionListener; import net.rim.device.api.system.WLANInfo; /** * Determines the current network data connection type and listens for changes * to network coverage. This class is intended for use through the Network * plug-in. */ public class ConnectionInfoAction { // Returned JSON attributes private static final String EVENT = "event"; private static final String TYPE = "type"; // Return types private static final String TYPE_UNKNOWN = "unknown"; private static final String TYPE_ETHERNET = "ethernet"; private static final String TYPE_WIFI = "wifi"; private static final String TYPE_2G = "2g"; private static final String TYPE_3G = "3g"; private static final String TYPE_4G = "4g"; private static final String TYPE_NONE = "none"; // Network status event values private static final String OFFLINE = "offline"; private static final String ONLINE = "online"; // Network service support constants private static final int DATA = RadioInfo.NETWORK_SERVICE_DATA; private static final int TWO_G = RadioInfo.NETWORK_SERVICE_DATA | RadioInfo.NETWORK_SERVICE_EDGE; private static final int THREE_G = RadioInfo.NETWORK_SERVICE_EVDO_REV0 | RadioInfo.NETWORK_SERVICE_EVDO_REV0_ONLY | RadioInfo.NETWORK_SERVICE_EVDO_REVA | RadioInfo.NETWORK_SERVICE_EVDO_REVA_ONLY | RadioInfo.NETWORK_SERVICE_UMTS; // Listeners used to detect network changes private RadioStatusListener radioListener = null; private WLANConnectionListener wlanListener = null; private CoverageStatusListener coverageListener = null; // Variable indicating whether the user has disabled mobile data private Boolean dataDisabled = Boolean.FALSE; // The set of call back IDs to send results to. Using Hashtable because // BlackBerry does not support Collections. There should only ever be one // call back ID, but this allows multiple. private Hashtable callbackIds = new Hashtable(); private String prevType = TYPE_NONE; private String prevEvent = OFFLINE; /** * Determines the current network data connection type. Listeners are * registered to return additional results when network state changes. * * @param callbackId * The success call back ID to receive network type results. * @return A PluginResult object with the success or failure result of the * operation. A success result includes information about the * currently active network type. */ protected PluginResult getConnectionInfo(String callbackId) { // Ensure that the dataDisabled variable is initialized. setDataDisabled(CoverageInfo.getCoverageStatus(), false); // Add the network change listeners if they have not been added. addListeners(callbackId); // Retrieve the current active connection type and build the return // result. String type = getConnectionType(true, true); JSONObject connectInfo = new JSONObject(); try { connectInfo.put(TYPE, type); } catch (JSONException e) { Logger.error("JSONException: " + e.getMessage()); return new PluginResult(PluginResult.Status.JSON_EXCEPTION, "JSONException: " + e.getMessage()); } PluginResult result = new PluginResult(PluginResult.Status.OK, connectInfo); // Need to keep the call back since listeners have been registered to // fire events on the specified call back ID. result.setKeepCallback(true); return result; } /** * Removes all coverage listeners and clears the list of call back IDs. This * method should be invoked when the Network plug-in's onDestroy is called. */ protected synchronized void shutdown() { if (radioListener != null) { Application.getApplication().removeRadioListener(radioListener); radioListener = null; } if (wlanListener != null) { WLANInfo.removeListener(wlanListener); wlanListener = null; } if (coverageListener != null) { CoverageInfo.removeListener(coverageListener); coverageListener = null; } callbackIds.clear(); } /** * Adds a RadioStatusListener, WLANConnectionListener and * CoverageStatusListener to listen for various network change events. The * listeners are only registered if they have not already been added. Each * listener is used to detect different types of network change events. * * RadioStatusListener - Detects changes in cellular data coverage. * WLANConnectionListener - Detects changes in wifi coverage. * CoverageStatusListener - Used to detect changes in the mobile data config * * @param callbackId * The reference point to call back when a listener event occurs. */ private synchronized void addListeners(String callbackId) { callbackIds.put(callbackId, callbackId); if (radioListener == null) { radioListener = new RadioStatusListener() { public void baseStationChange() {} public void networkScanComplete(boolean success) {} public void networkServiceChange(int networkId, int service) { // Cellular data change detected. If the user hasn't // disabled mobile data and wifi is not currently in use // return a result indicating the cellular data coverage // change. if (dataDisabled == Boolean.FALSE && WLANInfo.getWLANState() != WLANInfo.WLAN_STATE_CONNECTED) { if ((service & DATA) == 0) { sendResult(TYPE_NONE, OFFLINE); } else { // In the case where cell data and wifi was turned // off and then the user disabled mobile data // configuration, the mobile data config disablement // by the user isn't detected by the coverage status // listener so dataDisabled may not be accurate. // When service data is activated, have to make sure // that dataDisabled is properly set. setDataDisabled(CoverageInfo.getCoverageStatus(), false); if (dataDisabled == Boolean.FALSE) { sendResult(getConnectionType(false, true), ONLINE); } } } } public void networkStarted(int networkId, int service) {} public void networkStateChange(int state) {} public void pdpStateChange(int apn, int state, int cause) {} public void radioTurnedOff() {} public void signalLevel(int level) {} }; Application.getApplication().addRadioListener(radioListener); } if (wlanListener == null) { wlanListener = new WLANConnectionListener() { public void networkConnected() { if (dataDisabled == Boolean.FALSE) { sendResult(TYPE_WIFI, ONLINE); } } public void networkDisconnected(int reason) { // Wifi was disconnected, if the user hasn't disabled mobile // data, check if cellular data coverage exists. if (dataDisabled == Boolean.FALSE) { String type = getConnectionType(false, true); String event = OFFLINE; if (!TYPE_NONE.equals(type)) { event = ONLINE; } sendResult(type, event); } } }; WLANInfo.addListener(wlanListener); } if (coverageListener == null) { coverageListener = new CoverageStatusListener() { public void coverageStatusChanged(int newCoverage) { // When coverage changes, check to determine if it is due // to the user disabling mobile data through configuration // flag. setDataDisabled(newCoverage, true); } }; CoverageInfo.addListener(coverageListener); } } /** * Determine the type of connection currently being used for data * transmission on the device. If the user has disabled mobile data then * TYPE_NONE is returned regardless of cellular or wifi radio state as this * is the way the browser behaves. Otherwise, wifi and/or cellular radios * are queried for state based on the passed in parameters. * * @param checkWLAN * Determines whether wifi radio state is queried or not. * @param checkCell * Determines whether cellular radio state is queried or not. * @return A string indicating one of the defined network connections types. */ private String getConnectionType(boolean checkWLAN, boolean checkCell) { String networkType = TYPE_NONE; if (dataDisabled == Boolean.FALSE) { // Detect if wifi is active and connected. If wifi is active it // takes precedence over cellular data transmission. if (checkWLAN && WLANInfo.getWLANState() == WLANInfo.WLAN_STATE_CONNECTED) { networkType = TYPE_WIFI; } if (checkCell && TYPE_NONE.equals(networkType) && RadioInfo.isDataServiceOperational()) { int activeServices = RadioInfo.getNetworkService(); networkType = TYPE_UNKNOWN; if ((activeServices & THREE_G) != 0) { networkType = TYPE_3G; } else if ((activeServices & TWO_G) != 0) { networkType = TYPE_2G; } } } return networkType; } /** * Helper function to build and send the PluginResult to the saved call back * IDs. * * @param type * The network connection type. This value should be null if the * specified event is "offline". * @param event * The network event. */ private void sendResult(String type, String event) { JSONObject connectionInfo = new JSONObject(); try { connectionInfo.put(TYPE, type); connectionInfo.put(EVENT, event); } catch (JSONException e) { Logger.error("JSONException: " + e.getMessage()); for (Enumeration callbacks = this.callbackIds.elements(); callbacks .hasMoreElements();) { String callbackId = (String) callbacks.nextElement(); PhoneGapExtension.invokeErrorCallback(callbackId, new PluginResult(PluginResult.Status.JSON_EXCEPTION, "JSONException: " + e.getMessage())); } return; } // Only send the event if it is different then the last sent event. synchronized (prevType) { if (prevType != null && prevEvent != null && prevType.equals(type) && prevEvent.equals(event)) { return; } else { prevType = type; prevEvent = event; } } PluginResult result = new PluginResult(PluginResult.Status.OK, connectionInfo); // Must keep the call back active for future events. result.setKeepCallback(true); // Iterate through the saved call back IDs. Really should only ever be // one. for (Enumeration callbacks = this.callbackIds.elements(); callbacks .hasMoreElements();) { String callbackId = (String) callbacks.nextElement(); PhoneGapExtension.invokeSuccessCallback(callbackId, result); } } /** * Determines if the user has disabled mobile data through the user level * configuration panels and optionally returns an "online" or "offline" * result. * * @param newCoverage * A bit mask of CoverageInfo.COVERAGE_* flags indicating the * current coverage. * @param returnResult * If true, return a result based on the value of the mobile data * configuration. */ private void setDataDisabled(int newCoverage, boolean returnResult) { boolean isRadioData = (RadioInfo.getNetworkService() & DATA) != 0; boolean wlanConnected = WLANInfo.getWLANState() == WLANInfo.WLAN_STATE_CONNECTED; boolean eventDetected = false; String event = OFFLINE; // Note: To detect that mobile data has been disabled through // configuration, determine if the current coverage is // CoverageInfo.COVERAGE_NONE AND that either cellular or wifi radios // are currently connected. This is because the low level radio routines // will return a connected state even when mobile data is disabled // through configuration. synchronized (dataDisabled) { if (newCoverage == CoverageInfo.COVERAGE_NONE && (isRadioData || wlanConnected)) { if (dataDisabled == Boolean.FALSE) { Logger.log("Mobile data was disabled by the user through configuration."); dataDisabled = Boolean.TRUE; eventDetected = true; } } else if (dataDisabled == Boolean.TRUE) { Logger.log("Mobile data was enabled by the user."); dataDisabled = Boolean.FALSE; event = ONLINE; eventDetected = true; } } if (returnResult && eventDetected) { // The user has enabled/disabled mobile data. Return a result // indicating the current network state. String type = getConnectionType(true, true); sendResult(type, event); } } }