/*
* Funambol is a mobile platform developed by Funambol, Inc.
* Copyright (C) 2003 - 2008 Funambol, Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by
* the Free Software Foundation with the addition of the following permission
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
* WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, see http://www.gnu.org/licenses or write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA.
*
* You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
* 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Powered by Funambol" logo. If the display of the logo is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Powered by Funambol".
*
*
*/
package com.funambol.util;
import java.io.IOException;
import javax.microedition.io.Connector;
import javax.microedition.io.Connection;
import com.funambol.platform.HttpConnectionAdapter;
import com.funambol.platform.SocketAdapter;
import com.funambol.platform.HttpConnectionAdapterWrapper;
import com.funambol.platform.net.ProxyConfig;
/**
* Controls all of the connections requested by the API implementations.
* It is strongly recommended to use this class instead of the direct call to
* the Connector.open(String url) method. This class is based on the singleton
* pattern: it has private constructor and just one instance to be referenced
* calling the method ConnectionManager.getInstance() by other classes. The
* connection logic switch between all of the BlackberryConfiguration object
* returned by the Connection config class. The usage of the returned
* configuration is the following:
* <br>
* </br>
*
* <br>1. WIFI Network - just on the wifi capable devices, skipped if the wifi
* bearer is not present;
* </br>
* <br>
* </br>
* <br>2. Custom TCP configuration - Manually configured by the user
* </br>
* <br>
* </br>
* <br>3. APN gateway configuration - Carrier's Country based Configuration
* </br>
* <br>
* </br>
* <br>4. Blackberry ServiceBook configuration WAP Transport entry - Blackberry
* ServiceBook based Configuration
* </br>
* <br>
* </br>
* <br>NOTE 1: When a wifi network is no more available the system tries to
* configure a GPRS data connection in the same session.
* </br>
* <br>
* </br>
* <br>NOTE 2: When the Manually configured TCP configuration is cancelled or become
* invalid (i.e.: an invalid APN is substituted to a valid one), the TCP
* configuration won't ever be used during the same session but the system will
* switch other provided configurations.
* </br>
* <br>
* </br>
* <br>Note 3:When a configuration that is not WIFI or TCP based is validated, that
* configuration will be used for the entire session.
* </br>
*/
public class ConnectionManager {
private static final String TAG_LOG = "ConnectionManager";
/**String representation of SMS connection*/
private static final String SMS_URL = "sms://:";
/**WORKING configurations ID*/
protected static int workingConfigID = ConnectionConfig.CONFIG_NONE;
/**Array with possible blackberry configuration*/
private static BlackberryConfiguration[] configurations = null;
/**The unique instance of this clas*/
private static ConnectionManager instance = null;
/**ConnectionListener*/
private ConnectionListener connectionListener = new BasicConnectionListener();
private String connectionParameters = null;
/**
* Singleton realization: Private constructor
* Use getInstance() method to acces the public methods
*/
protected ConnectionManager() {
configurations = ConnectionConfig.getBlackberryConfigurations();
}
/**
* Singleton implementation:
* @return the current instance of this class or a new instance if it the
* current instance is null
*/
public static ConnectionManager getInstance() {
if (instance == null) {
Log.trace(TAG_LOG, "Creating new connection manager");
instance = new ConnectionManager();
instance.setConnectionListener(new BasicConnectionListener());
}
return instance;
}
/**
* Open up a connection to the given url with the default
* CLDC 1.0 API default values for the stream (READ_WRITE) and the value
* TRUE for the availability of InterruptedIOException to be thrown
* @param url The URL for the connection
* @return the connection url with the given parameters
* @throws java.io.IOException
*/
public synchronized Connection open(String url) throws IOException {
return open(url, Connector.READ_WRITE, true);
}
/**
* Open up a connection to the given url with the given access accessMode and
* @param url The URL for the connection
* @param accessMode the access accessMode that can be READ, WRITE, READ_WRITE
* @param enableTimeoutException A flag to indicate that the caller wants to
* enable the throwing of InterruptedIOException when a connection timeout
* occurs
* @return Connection related to the given parameters
* @throws java.io.IOException
*/
public synchronized Connection open(String url, int accessMode,
boolean enableTimeoutException)
throws IOException {
//A message connection is required. It needs no parameters
if (url.indexOf(SMS_URL)>0) {
return Connector.open(url);
}
if (this.connectionParameters != null) {
Log.debug(TAG_LOG, "Fixed Apn parameters detected for tis connection: "
+ url + this.connectionParameters);
return Connector.open(url + this.connectionParameters, accessMode,
enableTimeoutException);
}
//If the GPRS coverage was lost the ServiceBook could have changed
//A refesh is needed
Log.debug(TAG_LOG, "Refreshing the configuration");
ConnectionConfig.refreshServiceBookConfigurations();
//Just displays the network coverage
Log.trace(TAG_LOG, "Current network informations:\n"
+ BlackberryUtils.getNetworkCoverageReport());
//Checks the wifi status and removes it if the bearer is offline and
//it is the current working configuration
Log.trace(TAG_LOG, "Checking wifi status");
//Denies the wifi usage permission if the bearer is not available or
//turned off
if (!BlackberryUtils.isWifiActive()||!BlackberryUtils.isWifiAvailable()) {
Log.debug(TAG_LOG, "checkWifiStatus Wifi not available");
//switch to the TCPConfiguration if the wifi network is not
//available and the last working configuration was set to WIFI
if (workingConfigID==ConnectionConfig.WIFI_CONFIG) {
workingConfigID++;//=ConnectionConfig.CONFIG_NONE;
}
}
//if the GPRS bearer is not available and wifi is available, then it
//become the working id
if (workingConfigID > 0 && BlackberryUtils.isWapGprsDataBearerOffline()) {
Log.debug(TAG_LOG, "GPRS bearer is offline: switching to wifi if available");
if (BlackberryUtils.isWifiActive() && BlackberryUtils.isWifiAvailable()) {
Log.debug(TAG_LOG, "WIFI bearer is online: changing working configuration");
workingConfigID=ConnectionConfig.WIFI_CONFIG;
} else {
//Doesn't try the connection because in this case there is no
//bearer available.
Log.debug(TAG_LOG, "WIFI bearer is offline: "
+ "no suitable bearer were found. Throwing IOException");
throw new IOException();
}
}
if (workingConfigID < 0) {
Log.debug(TAG_LOG, "Setting up the connection...");
return setup(url, accessMode, enableTimeoutException);
} else {
Log.trace(TAG_LOG, "Working Configuration already assigned - ID="
+ workingConfigID);
Log.trace(TAG_LOG, "Using configuration: ID="
+ workingConfigID + " - Description "
+ configurations[workingConfigID].getDescription());
Connection ret = null;
try {
Log.debug(TAG_LOG, "Opening connection with: "
+ configurations[workingConfigID].getDescription());
String fullUrl = url + configurations[workingConfigID].getUrlParameters();
Log.trace(TAG_LOG, "Opening url: " + fullUrl);
ret = Connector.open(fullUrl, accessMode, enableTimeoutException);
} catch (Exception ioe) {
Log.debug(TAG_LOG, "Error occured while accessing " +
"the network with the last working configuration " + ioe.toString());
// If the current configuration no longer works, we try all the
// known connections to see if we find one that works.
// Connections that have already been granted/denied do not
// result in an extra prompt to the user, we rather keep his
// previous answer.
workingConfigID=ConnectionConfig.CONFIG_NONE;
// Close the connection if it got opened
if (ret != null) {
try {
ret.close();
} catch (Exception e) {
Log.debug(TAG_LOG, "Setup Failed Closing connection: "
+ "setting up another configuration");
Log.debug(TAG_LOG, "Exception: " + e);
}
}
// If all the possible connections are not working, the setup
// will throw an exception resulting in an exception in the
// "open".
ret = setup(url, accessMode, enableTimeoutException);
}
return ret;
}
}
/**
* Add optional parameters (such as APN configurations or wifi interface) to
* the given url
* @param url is the request url without configurations parameters
* @return String representing the url with the optional parameter added
*/
private Connection setup(String url, int accessMode, boolean enableTimeoutException)
throws IOException {
Connection ret = null;
String requestUrl = null;
for (int i = 0; i < ConnectionConfig.MAX_CONFIG_NUMBER; i++) {
try {
Log.debug(TAG_LOG, "Looping configurations: "
+ (i+1) + "/" + ConnectionConfig.MAX_CONFIG_NUMBER);
//If the open operation fails a subclass of IOException is thrown by the system
if (isConfigurationAllowed(i)) {
Log.debug(TAG_LOG, "Configuration Allowed: " + (i+1));
workingConfigID = i % configurations.length;
String options = configurations[i].getUrlParameters();
Log.debug(TAG_LOG, "Using parameters: " + options);
requestUrl = url + options;
} else {
Log.debug(TAG_LOG, "Setup config " + (i+1) + " cannot be used.");
continue;
}
Log.debug(TAG_LOG, "Connecting to: " + requestUrl);
ret = Connector.open(requestUrl, accessMode, enableTimeoutException);
//If the open call is succesfull it could be useful to notify
//the current working configuration changes to the listener
connectionListener.connectionConfigurationChanged();
Log.debug(TAG_LOG, "Listener notified");
// If we connected with a BIS configuration, then the user is
// not on BES and we can safely disable the BES configuration.
// This is more than an optimization, it is required to
// guarantee the correct execution because users with direct TCP
// experiences fake connections and weird timeouts when BES is
// being used
if (ConnectionConfig.isDirectTCP(workingConfigID)) {
Log.debug(TAG_LOG, "Direct TCP works, disable BES configuration");
int besId = ConnectionConfig.getBESConfigurationID();
if (besId != -1) {
configurations[besId].setPermission(ConnectionConfig.PERMISSION_DENIED);
}
}
return ret;
} catch (Exception ioe) {
Log.debug(TAG_LOG, "Connection not opened at attempt " + (i + 1));
Log.debug(TAG_LOG, ioe.toString());
// Close the connection in case it got opened
if (ret != null) {
try {
ret.close();
} catch (Exception e) {
Log.debug(TAG_LOG, "Failed Closing connection: trying next");
Log.debug(TAG_LOG, "Exception: " + e);
}
}
ret = null;
}
}
if (ret != null) {
return ret;
} else {
workingConfigID = ConnectionConfig.CONFIG_NONE;
//Doesn't return a null connection but throws an IOException
throw new IOException("[ConnectionManager.setup]Cannot find a suitable configuration");
}
}
/**
* Return the Availability of a configurations
* @param configNumber the Configuration ID
* @return boolean true if the configurations can be used, false otherwise
*/
private boolean isConfigurationAllowed(int configNumber) {
Log.debug(TAG_LOG, "isConfigurationAllowed Config number: " + configNumber);
if (!ConnectionConfig.isAvailable(configNumber)) {
Log.debug(TAG_LOG, "isConfigurationAllowed Connection not available");
return false;
}
//Calculates the apn String
Log.debug(TAG_LOG, "Calculating APN");
String apn = ConnectionConfig.getAPNFromConfig(configNumber);
Log.debug(TAG_LOG, "APN Found: " + apn);
//Permission is denied
if (configurations[configNumber].getPermission()==ConnectionConfig.PERMISSION_DENIED){
Log.debug(TAG_LOG, "Permission DENIED for: " + apn);
return false;
}
//Permission is granted
if (configurations[configNumber].getPermission()==ConnectionConfig.PERMISSION_GRANTED){
Log.debug(TAG_LOG, "Permission GRANTED for: " + apn);
return true;
}
//null check is performed because the apn is returned as null when the
//mobile network is not available
if (apn==null) {
if (configNumber>ConnectionConfig.TCP_CONFIG) {
Log.debug(TAG_LOG, "Retrieved null APN: availability DENIED");
return false;
}
}
//Connection listener logic implemented for undefined permission when network is covered
if (configurations[configNumber].getPermission()==ConnectionConfig.PERMISSION_UNDEFINED){
Log.debug(TAG_LOG, "Permission not defined for: " + apn);
boolean isConfigurationAllowed = connectionListener.isConnectionConfigurationAllowed(apn);
if (isConfigurationAllowed) {
Log.debug(TAG_LOG, "Permission set to GRANTED");
configurations[configNumber].setPermission(ConnectionConfig.PERMISSION_GRANTED);
} else {
Log.debug(TAG_LOG, "Permission set to DENIED");
configurations[configNumber].setPermission(ConnectionConfig.PERMISSION_DENIED);
}
return isConfigurationAllowed;
}
Log.debug(TAG_LOG, "No suitable condition "
+ "to allow the configuration (" + (configNumber+1) + ") was found");
return false;
}
/**
* Accessor method to set the connection listener
* @param cl the connection listener to be set
*/
public void setConnectionListener(ConnectionListener cl) {
this.connectionListener = cl;
}
/**
* Accessor method to get the current connection listener
* @return ConnectionListener related to this ConnectionManager instance
*/
public ConnectionListener getConnectionListener() {
return connectionListener;
}
/**
* Set the Connection parameters for this connection given the apn string
* @param apn the fixed apn to be used.
*/
public void setFixedApn(String apn) {
if (apn!=null) {
this.connectionParameters = ";deviceside=true;apn=" + apn + ";WapGatewayAPN=" + apn+
";ConnectionTimeout=300000";
} else {
this.connectionParameters = null;
}
}
/**
* Disable the connection parameters for the fixed APN usage. Use this
* method after a setConnectionPaarameters call in order to use the device
* apn instead of the fixed one
*/
public void unsetFixedApn() {
this.connectionParameters=null;
}
/**
* Open up a connection to the given url with the given access accessMode and
* @param url The URL for the connection
* @param extra is some extra information that can be specified to specific
* implementations
* @param proxyConfig proxy configuration, null if no proxy is required
*
* @return the connection (cannot be null)
*
* @throws java.io.IOException
*/
public synchronized HttpConnectionAdapter openHttpConnection(String url, ProxyConfig proxyConfig, Object extra) throws IOException
{
if (this.connectionParameters != null) {
Log.debug(TAG_LOG, "Fixed Apn parameters detected for tis connection: "
+ url + this.connectionParameters);
HttpConnectionAdapter conn = createAdapter(extra);
conn.open(url + this.connectionParameters, proxyConfig);
return conn;
}
//If the GPRS coverage was lost the ServiceBook could have changed
//A refesh is needed
Log.debug(TAG_LOG, "Refreshing the configuration");
ConnectionConfig.refreshServiceBookConfigurations();
//Just displays the network coverage
Log.trace(TAG_LOG, "Current network informations:\n"
+ BlackberryUtils.getNetworkCoverageReport());
//Checks the wifi status and removes it if the bearer is offline and
//it is the current working configuration
Log.trace(TAG_LOG, "Checking wifi status");
//Denies the wifi usage permission if the bearer is not available or
//turned off
if (!BlackberryUtils.isWifiActive()||!BlackberryUtils.isWifiAvailable()) {
Log.debug(TAG_LOG, "checkWifiStatus Wifi not available");
//switch to the TCPConfiguration if the wifi network is not
//available and the last working configuration was set to WIFI
if (workingConfigID==ConnectionConfig.WIFI_CONFIG) {
workingConfigID++;//=ConnectionConfig.CONFIG_NONE;
}
}
//if the GPRS bearer is not available and wifi is available, then it
//become the working id
if (workingConfigID > 0 && BlackberryUtils.isWapGprsDataBearerOffline()) {
Log.debug(TAG_LOG, "GPRS bearer is offline: switching to wifi if available");
if (BlackberryUtils.isWifiActive() && BlackberryUtils.isWifiAvailable()) {
Log.debug(TAG_LOG, "WIFI bearer is online: changing working configuration");
workingConfigID=ConnectionConfig.WIFI_CONFIG;
} else {
//Doesn't try the connection because in this case there is no
//bearer available.
Log.debug(TAG_LOG, "WIFI bearer is offline: "
+ "no suitable bearer were found. Throwing IOException");
throw new IOException();
}
}
if (workingConfigID < 0) {
Log.debug(TAG_LOG, "Setting up the connection...");
return setupHttpConnection(url, extra, proxyConfig);
} else {
Log.trace(TAG_LOG, "Working Configuration already assigned - ID="
+ workingConfigID);
Log.trace(TAG_LOG, "Using configuration: ID="
+ workingConfigID + " - Description "
+ configurations[workingConfigID].getDescription());
HttpConnectionAdapter ret = null;
try {
Log.debug(TAG_LOG, "Opening connection with: "
+ configurations[workingConfigID].getDescription());
String fullUrl = url + configurations[workingConfigID].getUrlParameters();
Log.trace(TAG_LOG, "Opening url: " + fullUrl);
ret = createAdapter(extra);
ret.open(fullUrl);
} catch (Exception ioe) {
Log.debug(TAG_LOG, "Error occured while accessing " +
"the network with the last working configuration");
// If the current configuration no longer works, we try all the
// known connections to see if we find one that works.
// Connections that have already been granted/denied do not
// result in an extra prompt to the user, we rather keep his
// previous answer.
workingConfigID=ConnectionConfig.CONFIG_NONE;
// Close the connection if it got opened
if (ret != null) {
try {
ret.close();
} catch (Exception e) {
Log.debug(TAG_LOG, "Setup Failed Closing connection: "
+ "setting up another configuration");
Log.debug(TAG_LOG, "Exception: " + e);
}
}
// If all the possible connections are not working, the setup
// will throw an exception resulting in an exception in the
// "open".
ret = setupHttpConnection(url, extra, proxyConfig);
}
return ret;
}
}
/**
* Open up a connection to the given url with the given access accessMode and
* @param url The URL for the connection
* @param extra is some extra information that can be specified to specific
* implementations
*
* @return the connection (cannot be null)
*
* @throws java.io.IOException
*/
public synchronized HttpConnectionAdapter openHttpConnection(String url, Object extra) throws IOException
{
return openHttpConnection(url, null, extra);
}
/**
* Open a socket connection to the given URL
*
* @param addr is the server address
* @param port the port
* @param mode can be READ_WRITE
* @param timeout enable timeout on IO operations
* @param proxyConfig proxy configuration, null if no proxy is required
*/
public SocketAdapter openSocketConnection(String addr, int port, int mode, boolean timeout, ProxyConfig proxyConfig) throws IOException {
SocketAdapter res = new SocketAdapter(addr, port, mode, timeout, proxyConfig);
return res;
}
/**
* Open a socket connection to the given URL
*
* @param addr is the server address
* @param port the port
* @param mode can be READ_WRITE
* @param timeout enable timeout on IO operations
*/
public SocketAdapter openSocketConnection(String addr, int port, int mode, boolean timeout) throws IOException {
return openSocketConnection(addr, port, mode, timeout, null);
}
/**
* Add optional parameters (such as APN configurations or wifi interface) to
* the given url
* @param url is the request url without configurations parameters
* @return String representing the url with the optional parameter added
*/
private HttpConnectionAdapter setupHttpConnection(String url, Object extra, ProxyConfig proxyConfig)
throws IOException {
HttpConnectionAdapter ret = null;
String requestUrl = null;
for (int i = 0; i < ConnectionConfig.MAX_CONFIG_NUMBER; i++) {
try {
Log.debug(TAG_LOG, "Looping configurations: "
+ (i+1) + "/" + ConnectionConfig.MAX_CONFIG_NUMBER);
//If the open operation fails a subclass of IOException is thrown by the system
if (isConfigurationAllowed(i)) {
Log.debug(TAG_LOG, "Configuration Allowed: " + (i+1));
workingConfigID = i % configurations.length;
String options = configurations[i].getUrlParameters();
Log.debug(TAG_LOG, "Using parameters: " + options);
requestUrl = url + options;
} else {
Log.debug(TAG_LOG, "Setup config " + (i+1) + " cannot be used.");
continue;
}
Log.debug(TAG_LOG, "Connecting to: " + requestUrl);
ret = createAdapter(extra);
ret.open(requestUrl, proxyConfig);
//If the open call is succesfull it could be useful to notify
//the current working configuration changes to the listener
connectionListener.connectionConfigurationChanged();
Log.debug(TAG_LOG, "Listener notified");
// If we connected with a BIS configuration, then the user is
// not on BES and we can safely disable the BES configuration.
// This is more than an optimization, it is required to
// guarantee the correct execution because users with direct TCP
// experiences fake connections and weird timeouts when BES is
// being used
if (ConnectionConfig.isDirectTCP(workingConfigID)) {
Log.debug(TAG_LOG, "Direct TCP works, disable BES configuration");
int besId = ConnectionConfig.getBESConfigurationID();
if (besId != -1) {
configurations[besId].setPermission(ConnectionConfig.PERMISSION_DENIED);
}
}
return ret;
} catch (Exception ioe) {
Log.debug(TAG_LOG, "Connection not opened at attempt " + (i + 1));
Log.debug(TAG_LOG, ioe.toString());
// Close the connection in case it got opened
if (ret != null) {
try {
ret.close();
} catch (Exception e) {
Log.debug(TAG_LOG, "Failed Closing connection: trying next");
Log.debug(TAG_LOG, "Exception: " + e);
}
}
ret = null;
}
}
if (ret != null) {
return ret;
} else {
workingConfigID = ConnectionConfig.CONFIG_NONE;
//Doesn't return a null connection but throws an IOException
throw new IOException("[ConnectionManager.setup]Cannot find a suitable configuration");
}
}
private HttpConnectionAdapter createAdapter(Object extra) {
HttpConnectionAdapter res = null;
if (extra != null && extra instanceof String) {
String e = (String)extra;
if (e.startsWith("wrapper")) {
res = new HttpConnectionAdapterWrapper();
}
}
if (res == null) {
res = new HttpConnectionAdapter();
}
return res;
}
//////////////////////// This code is for automatic tests only /////////////////////////
public void setBreakInfo(String breakOnPhase, String breakOnKey, int breakOnPos, int breakOnIdx) {
}
}