Mobile internet (GPRS, EDGE, etc) is typically more expensive and slower than internet provided by wlan access points (via wi-fi), but has much greater coverage. Most android devices have mobile internet. It's configured from
Since as typical android device has possibility to establish connection to wlan access point (i.e. wi-fi internet connection) and use mobile operator internet (i.e. mobile internet) option to establish 2 internet connection simultaneously theoretical exists. But to dot the i's and cross the t's let's consider typical rules for internet connection, which inherent not just for Android OS, but for most mobile operation systems:
This rules are quite logical and make sense. Only one internet connection can be established to save device battery. Wi-fi has higher priority because it's faster and cheaper than mobile internet. And since wi-fi connection has higher priority, points 3 and 4 are quite obvious too. But in case when you need to circumvent these rules you will be faced with serious problems (especially given the fact that the there is
no public api in android sdk to manage mobile network connection) and all you remain to do is rely on funny tricks, some of which will be discussed below. But since all this tricks are based on reflection, dirty hacks and hidden api, there is no there is no guarantee, that this will works on your certain device (in my experience, it depends on vendor and device model). Furthermore, I don't recommend you to use this things in your production application, they are (this tricks) interesting only for experiments.
So, let's start from permissions essential for these experiments:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE"/>
<uses-permission android:name="android.permission.WRITE_APN_SETTINGS"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
Next moment - we need to enable mobile data connection. It should be noted, that this operation a little bit differently performed for old and for new sdk version. For sdk 2.1 and earlier you should do something like this:
private void setDataConnection(boolean dataConnection) {
Method dataConnSwitchMethod;
Class telephonyManagerClass;
Object ITelephonyStub;
Class ITelephonyClass;
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
try {
telephonyManagerClass = Class.forName(telephonyManager.getClass().getName());
Method getITelephonyMethod = telephonyManagerClass.getDeclaredMethod("getITelephony");
getITelephonyMethod.setAccessible(true);
ITelephonyStub = getITelephonyMethod.invoke(telephonyManager);
ITelephonyClass = Class.forName(ITelephonyStub.getClass().getName());
String invokeMethodName = dataConnection ? "enableDataConnectivity" : "disableDataConnectivity";
Log.d(TAG, invokeMethodName);
dataConnSwitchMethod = ITelephonyClass.getDeclaredMethod(invokeMethodName);
dataConnSwitchMethod.setAccessible(true);
dataConnSwitchMethod.invoke(ITelephonyStub);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
As you can see, we are getting
ITelephony aidl interface to enable or disable mobile data connections:
/**
* Allow mobile data connections.
*/
boolean enableDataConnectivity();
/**
* Disallow mobile data connections.
*/
boolean disableDataConnectivity();
Starting from sdk 2.2 you can use the
IConnectivityManager#setMobileDataEnabled method. It's hidden in API too, so we have to use reflection again:
private void setMobileDataEnabled(boolean dataConnection) {
try {
final ConnectivityManager conman = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
final Class conmanClass = Class.forName(conman.getClass().getName());
final Field iConnectivityManagerField = conmanClass.getDeclaredField("mService");
iConnectivityManagerField.setAccessible(true);
final Object iConnectivityManager = iConnectivityManagerField.get(conman);
final Class iConnectivityManagerClass = Class.forName(iConnectivityManager.getClass().getName());
final Method setMobileDataEnabledMethod = iConnectivityManagerClass.getDeclaredMethod("setMobileDataEnabled", Boolean.TYPE);
setMobileDataEnabledMethod.setAccessible(true);
setMobileDataEnabledMethod.invoke(iConnectivityManager, dataConnection);
} catch (Exception e) {
...
}
}
On some devices this operation requires
android.permission.WRITE_SECURE_SETTINGS, which maybe granted only for system applications, so for such cases all next tricks will have no any sense. Only thing you can do - root your device and install app to /system folder (using
adb shell push app.apk /system command).
So, after mobile data connection is enabled, we will try add
APN we want to use:
private static final Uri APN_TABLE_URI = Uri.parse("content://telephony/carriers");
public static final String OPERATOR_NUMERIC_KEY = "gsm.sim.operator.numeric";
public int addAPN() {
int id = -1;
ContentResolver resolver = getContentResolver();
ContentValues values = new ContentValues();
values.put("name", appName);
values.put("apn", accessPointName);
values.put("user", userName);
values.put("password", password);
// read mobile operator numeric info using shell command getprop
String numeric = getSystemProperty(OPERATOR_NUMERIC_KEY);
String mcc = "";
String mnc = "";
try {
mcc = numeric.substring(0, 3);
mnc = numeric.substring(3, 5);
} catch (Exception e) {
...
}
values.put("mcc", mcc);
values.put("mnc", mnc);
values.put("numeric", numeric);
Cursor cursor = null;
try {
// insert apn
Uri newRow = resolver.insert(APN_TABLE_URI, values);
if (null != newRow) {
cursor = resolver.query(newRow, null, null, null, null);
Log.d(TAG, "Newly added APN:");
// Obtain the apn id
int idIndex = cursor.getColumnIndex("_id");
cursor.moveToFirst();
id = cursor.getShort(idIndex);
Log.d(TAG, "New ID: " + id + ": Inserting new APN succeeded!");
}
}
catch (Exception e) {
Log.d(TAG, e.getMessage());
}
if (null != cursor) {
cursor.close();
}
return id;
}
...
public static String getSystemProperty(String key) {
try {
String line;
String formattedKey = "[" + key + ']';
java.lang.Process p = Runtime.getRuntime().exec("getprop");
BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream()));
while (null != (line = input.readLine())) {
String[] property = line.split(":");
if (formattedKey.equals(property[0].trim())) {
return property[1].trim().substring(1, property[1].length() - 2);
}
}
input.close();
}
catch (Exception err) {
err.printStackTrace();
}
return null;
}
I think all is obvious here.
ContentResolver is used to access table of system
APNs. The only thing that deserves attention here is
// read mobile operator numeric info using shell command getprop
String numeric = getSystemProperty(OPERATOR_NUMERIC_KEY);
We are extracting system property
gsm.sim.operator.numeric and parsing result to get
Mobile Network Code (mnc) and
Mobile Country Code (mcc) - these values are essential for
APN.
Now we need to set our inserted
APN to be default. From user interface it's looks like to select appropriate radio button on list of available
APNs.
Let's take a look how can we do this programmatically:
private static final Uri PREFERRED_APN_URI = Uri.parse("content://telephony/carriers/preferapn");
public boolean setActiveAPN(int id) {
boolean result = false;
ContentResolver resolver = getContentResolver();
ContentValues values = new ContentValues();
values.put("apn_id", id);
try {
resolver.update(PREFERRED_APN_URI, values, null, null);
Cursor cursor = resolver.query(
PREFERRED_APN_URI,
new String[]{"name", "apn"},
"_id=" + id,
null,
null);
if (null != cursor) {
result = true;
cursor.close();
}
}
catch (Exception e) {
Log.d(TAG, e.getMessage());
}
return result;
}
To
setActiveAPN method we need to pass
id. We can use identifier of
APN, created by
addAPN function or any other existed
APN.
So, now, after mobile connection is enabled and you add
APN and made it default you device will try to establish connection (of course, there should not be
wi-fi connection). If you provide correct
APN your device will have mobile internet after your credential will be verified.
But there is one more trick I want to show. It allow to raise mobile connection even with wifi connected.
ConnectivityManager#startUsingNetworkFeature method is used to request network for you application. You need to specify which network the request pertains to (first param) and the name of the feature to be used (second param). Typical request will looks like:
connectivityManager.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, "enableHIPRI");
This means, that you want to enable High Priority (HIPRI) Mobile data connection over mobile network. More about first argument you can read in documentation to
ConnectivityManager. List of available features you can found in internal interface
Phone:
// "Features" accessible through the connectivity manager
static final String FEATURE_ENABLE_MMS = "enableMMS";
static final String FEATURE_ENABLE_SUPL = "enableSUPL";
static final String FEATURE_ENABLE_DUN = "enableDUN";
static final String FEATURE_ENABLE_HIPRI = "enableHIPRI";
static final String FEATURE_ENABLE_DUN_ALWAYS = "enableDUNAlways";
static final String FEATURE_ENABLE_FOTA = "enableFOTA";
static final String FEATURE_ENABLE_IMS = "enableIMS";
static final String FEATURE_ENABLE_CBS = "enableCBS";
But you need to remember, that all requested network features are active only when your application is 'alive' (i.e. not stopped or killed by system).