I need my app to reset the device's mobile network signal. This would have the same effect as toggling airplane mode where connectivity is temporarily lost, a new IP address is assigned upon reconnection, and the LTE/signal icon in the status bar should disappear and then reappear upon reconnection. I found an app on the Play Store that I tested on my phone running Android 4.4.4 with CyanogenMod and it does exactly this, but I am unsure as to how I can implement this in my own app. I think it's related to the CHANGE_NETWORK_STATE permission. I am seeking documentation or some simple example code that will reset the network connection.
Note that I am not specifically trying to toggle airplane mode, but rather to reset the mobile data in the way that the app linked above does, as I have tested that it indeed works without even requiring root privileges.
Lollipop support requires a new System level privledge android.permission.MODIFY_PHONE_STATE to work.
private static boolean setMobileConnectionEnabled(Context context, boolean enabled)
{
try{
// Requires: android.permission.CHANGE_NETWORK_STATE
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD){
// pre-Gingerbread sucks!
final TelephonyManager telMgr = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
final Method getITelephony = telMgr.getClass().getDeclaredMethod("getITelephony");
getITelephony.setAccessible(true);
final Object objITelephony = getITelephony.invoke(telMgr);
final Method toggleDataConnectivity = objITelephony.getClass()
.getDeclaredMethod(enabled ? "enableDataConnectivity" : "disableDataConnectivity");
toggleDataConnectivity.setAccessible(true);
toggleDataConnectivity.invoke(objITelephony);
}
// Requires: android.permission.CHANGE_NETWORK_STATE
else if(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP){
final ConnectivityManager connMgr = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
// Gingerbread to KitKat inclusive
final Field serviceField = connMgr.getClass().getDeclaredField("mService");
serviceField.setAccessible(true);
final Object connService = serviceField.get(connMgr);
try{
final Method setMobileDataEnabled = connService.getClass()
.getDeclaredMethod("setMobileDataEnabled", Boolean.TYPE);
setMobileDataEnabled.setAccessible(true);
setMobileDataEnabled.invoke(connService, Boolean.valueOf(enabled));
}
catch(NoSuchMethodException e){
// Support for CyanogenMod 11+
final Method setMobileDataEnabled = connService.getClass()
.getDeclaredMethod("setMobileDataEnabled", String.class, Boolean.TYPE);
setMobileDataEnabled.setAccessible(true);
setMobileDataEnabled.invoke(connService, context.getPackageName(), Boolean.valueOf(enabled));
}
}
// Requires: android.permission.MODIFY_PHONE_STATE (System only, here for completions sake)
else{
// Lollipop and into the Future!
final TelephonyManager telMgr = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
final Method setDataEnabled = telMgr.getClass().getDeclaredMethod("setDataEnabled", Boolean.TYPE);
setDataEnabled.setAccessible(true);
setDataEnabled.invoke(telMgr, Boolean.valueOf(enabled));
}
return true;
}
catch(NoSuchFieldException e){
Log.e(TAG, "setMobileConnectionEnabled", e);
}
catch(IllegalAccessException e){
Log.e(TAG, "setMobileConnectionEnabled", e);
}
catch(IllegalArgumentException e){
Log.e(TAG, "setMobileConnectionEnabled", e);
}
catch(NoSuchMethodException e){
Log.e(TAG, "setMobileConnectionEnabled", e);
}
catch(InvocationTargetException e){
Log.e(TAG, "setMobileConnectionEnabled", e);
}
return false;
}
Permission required.
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
Starting with 4.4.2, one can no longer acquire permissions to toggle airplane mode or mobile data. However, using reflection, one can work around that limitation.
Here's how to toggle airplane mode:
Toggle airplane mode in Android
Here's a method to toggle mobile data pre 4.4.2 and post (using reflection):
Toggle mobile data programmatically on Android 4.4.2
Note, I found all that in less than 3 minutes by searching on my favorite search engine for "android toggle airplane mode" and "android toggle mobile data".
Note 2: You can use reflection for airplane mode toggling too, but you may need to do some digging to find the necessary ASOP API or see if someone else (like CyanogenMod) has already done it.
Related
So I am developing an app which works as device owner on the specific Android device. This app is not available on the play store, but gets transferred with a provisioning app from a different device via NFC. Since those devices will be very specific to certain tasks (scanning NFC tags), I want to enable and disable a few things from the very beginning.
I want to disable sound:
devicePolicyManager.setMasterVolumeMuted(adminComponentName, true);
But this doesn't seem to work at all, but no exception either.
But what I really want to do is enabling mobile Data and Roaming, the SIM cards which we are using support that.
devicePolicyManager.setSecureSetting(adminComponentName, Settings.Global.DATA_ROAMING, String.valueOf(1));
devicePolicyManager.setSecureSetting(adminComponentName,"mobile_data",String.valueOf(1));
But sadly, those two lines of code throw a security exception:
java.lang.SecurityException: Permission denial: Device owners cannot update mobile_data
Interestingly, inserting APNs work (later in the code) Any chance to be able to turn on mobile data and data roaming as a device admin/owner? I mean, thats the whole purpose of being a device admin, right?
Here is the full code for reference: (the parts which make the app crash are commented out)
public static void enableRestrictedAppsAndSettings(Activity activity) {
ComponentName adminComponentName = DeviceAdminReceiver.getComponentName(activity);
DevicePolicyManager devicePolicyManager = (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// disable keyguard and sound
devicePolicyManager.setKeyguardDisabled(adminComponentName, true);
devicePolicyManager.setMasterVolumeMuted(adminComponentName, true);
devicePolicyManager.setSecureSetting(adminComponentName, Settings.Secure.LOCATION_MODE, String.valueOf(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY));
//devicePolicyManager.setSecureSetting(adminComponentName, Settings.Global.DATA_ROAMING, String.valueOf(1));
//devicePolicyManager.setSecureSetting(adminComponentName,"mobile_data",String.valueOf(1));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (devicePolicyManager.isDeviceOwnerApp(activity.getApplicationContext().getPackageName())) {
devicePolicyManager.enableSystemApp(adminComponentName,"com.sec.android.app.camera");
devicePolicyManager.clearUserRestriction(adminComponentName, UserManager.DISALLOW_DATA_ROAMING);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
L.debug("KIOSK", "APN");
ApnSetting apn;
TelephonyManager manager = (TelephonyManager)activity.getSystemService(Context.TELEPHONY_SERVICE);
if (manager.getSimState() == TelephonyManager.SIM_STATE_READY) {
String mcc = manager.getSimOperator().substring(0, 3);
String mnc = manager.getSimOperator().substring(3);
L.debug("KIOSK " + mcc + " "+mnc);
apn = new ApnSetting.Builder()
.setApnTypeBitmask(ApnSetting.TYPE_DEFAULT)
.setApnName("em")
.setEntryName("em")
.setOperatorNumeric(mcc + mnc) // this is a must its consists from Telephony.Carriers.MCC + Telephony.Carriers.MNC, In my case, I had to pad the MNC with a leading zero
.setProtocol(ApnSetting.PROTOCOL_IPV4V6) // this is a must
.setRoamingProtocol(ApnSetting.PROTOCOL_IPV4V6) // this is a must
.setCarrierEnabled(true)
.build();
devicePolicyManager.removeOverrideApn(adminComponentName,0);
devicePolicyManager.addOverrideApn(adminComponentName, apn);
devicePolicyManager.setOverrideApnsEnabled(adminComponentName, true);
}
}
}
}
Unfortunately the device owner has no access to mobile data status (you're right, weird restriction for the device owner app!).
However, you can still get the mobile data status and force the user to turn it on or off if the status is wrong. Here're the code samples (thanks to Test if background data and packet data is enabled or not).
public static boolean isMobileDataEnabled(Context context) {
ConnectivityManager cm = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
try {
Class clazz = Class.forName(cm.getClass().getName());
Method method = clazz.getDeclaredMethod("getMobileDataEnabled");
method.setAccessible(true); // Make the method callable
// get the setting for "mobile data"
return (Boolean) method.invoke(cm);
} catch (Exception e) {
// Let it will be true by default
return true;
}
}
This code works on Android 5-9 (not tested on Android 10 yet).
So you run a background service which performs this check once per some seconds and requires the user to turn on/off the mobile data in the status bar.
You can see how it is done by cloning this open source Android MDM (this is my project). The method is here: Utils.isMobileDataEnabled(Context context).
I have logged Issue 78084 with Google regarding the setMobileDataEnabled() method being no longer callable via reflection. It was callable since Android 2.1 (API 7) to Android 4.4 (API 19) via reflection, but as of Android L and later, even with root, the setMobileDataEnabled() method is not callable.
The official response is that the issue is "Closed" and the status set to "WorkingAsIntended". Google's simple explanation is:
Private APIs are private because they are not stable and might disappear without notice.
Yes, Google, we are aware of the risk of using reflection to call hidden method- even before Android came on the scene- but you need to provide a more solid answer as to alternatives, if any, for accomplishing the same result as setMobileDataEnabled(). (If you are displeased with Google's decision as I am, then log into Issue 78084 and star it as many as possible to let Google know the error of their way.)
So, my question to you is: Are we at a dead end when it comes to programmatically enable or disable mobile network function on an Android device? This heavy-handed approach from Google somehow does not sit well with me. If you have workaround for Android 5.0 (Lollipop) and beyond, I would love to hear your answer/discussion in this thread.
I have used the code below to see if the setMobileDataEnabled() method is available:
final Class<?> conmanClass = Class.forName(context.getSystemService(Context.CONNECTIVITY_SERVICE).getClass().getName());
final Field iConnectivityManagerField = conmanClass.getDeclaredField("mService");
iConnectivityManagerField.setAccessible(true);
final Object iConnectivityManager = iConnectivityManagerField.get(context.getSystemService(Context.CONNECTIVITY_SERVICE));
final Class<?> iConnectivityManagerClass = Class.forName(iConnectivityManager.getClass().getName());
final Method[] methods = iConnectivityManagerClass.getDeclaredMethods();
for (final Method method : methods) {
if (method.toGenericString().contains("set")) {
Log.i("TESTING", "Method: " + method.getName());
}
}
But it's not.
UPDATE: Currently, it's possible to toggle mobile network if the device is rooted. However, for non-rooted devices, it's still an investigative process as there is no universal method to toggle mobile network.
To extend Muzikant's Solution #2, can someone please try the solution below on an Android 5.0 rooted device (as I currently do not possess one) and let me know if it works or does not work.
To enable or disable mobile data, try:
// 1: Enable; 0: Disable
su -c settings put global mobile_data 1
su -c am broadcast -a android.intent.action.ANY_DATA_STATE --ez state 1
Note: The mobile_data variable can be found in Android API 21 source codes at /android-sdk/sources/android-21/android/provider/Settings.java and is declared as:
/**
* Whether mobile data connections are allowed by the user. See
* ConnectivityManager for more info.
* #hide
*/
public static final String MOBILE_DATA = "mobile_data";
While the android.intent.action.ANY_DATA_STATE Intent can be found in Android API 21 source codes at /android-sdk/sources/android-21/com/android/internal/telephony/TelephonyIntents.java and is declared as:
/**
* Broadcast Action: The data connection state has changed for any one of the
* phone's mobile data connections (eg, default, MMS or GPS specific connection).
*
* <p class="note">
* Requires the READ_PHONE_STATE permission.
* <p class="note">This is a protected intent that can only be sent by the system.
*
*/
public static final String ACTION_ANY_DATA_CONNECTION_STATE_CHANGED
= "android.intent.action.ANY_DATA_STATE";
UPDATE 1: If you don't want to implement the above Java codes in your Android application, then you can run the su commands via a shell (Linux) or command prompt (Windows) as follow:
adb shell "su -c 'settings put global mobile_data 1; am broadcast -a android.intent.action.ANY_DATA_STATE --ez state 1'"
Note: adb is located at /android-sdk/platform-tools/ directory. The settings command is only supported on Android 4.2 or later. Older Android version will report a "sh: settings: not found" error.
UPDATE 2: Another way to toggle mobile network on a rooted Android 5+ device would be to use the undocumented service shell command. The following command can be executed via ADB to toggle mobile network:
// 1: Enable; 0: Disable
adb shell "su -c 'service call phone 83 i32 1'"
Or just:
// 1: Enable; 0: Disable
adb shell service call phone 83 i32 1
Note 1: The transaction code 83 used in the service call phone command might change between Android versions. Please check com.android.internal.telephony.ITelephony for the value of the TRANSACTION_setDataEnabled field for your version of Android. Also, instead of hardcoding 83, you would be better off using Reflection to get the value of the TRANSACTION_setDataEnabled field. This way, it will work across all mobile brands running Android 5+ (If you don't know how to use Reflection to get the value of the TRANSACTION_setDataEnabled field, see solution from PhongLe below- save me from duplicating it here.) Important: Please note that transaction code TRANSACTION_setDataEnabled has only been introduced in Android 5.0 and later versions. Running this transaction code on earlier versions of Android will do nothing as the transaction code TRANSACTION_setDataEnabled does not exist.
Note 2: adb is located at /android-sdk/platform-tools/ directory. If you do not wish to use ADB, execute the method via su in your app.
Note 3: See UPDATE 3 below.
UPDATE 3: Many Android developers have emailed me questions regarding switching mobile network on/off for Android 5+, but instead of answering individual emails, I'll post my answer here so everyone can use it and adapt it for their Android apps.
First thing first, let's clear up some misconception and misunderstanding regarding:
svc data enable
svc data disable
The above methods would only turn background data on/off, not the subscription service, so the battery will drain a fair bit since the subscription service- an Android system service- will still be running in the background. For Android devices supporting multiple sim cards, this scenario is worse as the subscription service constantly scans for available mobile network(s) to use with the active SIM cards available in the Android device. Use this method at your own risk.
Now, the proper way to switch off mobile network, including its corresponding subscription service via the SubscriptionManager class introduced in API 22, is:
public static void setMobileNetworkfromLollipop(Context context) throws Exception {
String command = null;
int state = 0;
try {
// Get the current state of the mobile network.
state = isMobileDataEnabledFromLollipop(context) ? 0 : 1;
// Get the value of the "TRANSACTION_setDataEnabled" field.
String transactionCode = getTransactionCode(context);
// Android 5.1+ (API 22) and later.
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
SubscriptionManager mSubscriptionManager = (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
// Loop through the subscription list i.e. SIM list.
for (int i = 0; i < mSubscriptionManager.getActiveSubscriptionInfoCountMax(); i++) {
if (transactionCode != null && transactionCode.length() > 0) {
// Get the active subscription ID for a given SIM card.
int subscriptionId = mSubscriptionManager.getActiveSubscriptionInfoList().get(i).getSubscriptionId();
// Execute the command via `su` to turn off
// mobile network for a subscription service.
command = "service call phone " + transactionCode + " i32 " + subscriptionId + " i32 " + state;
executeCommandViaSu(context, "-c", command);
}
}
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
// Android 5.0 (API 21) only.
if (transactionCode != null && transactionCode.length() > 0) {
// Execute the command via `su` to turn off mobile network.
command = "service call phone " + transactionCode + " i32 " + state;
executeCommandViaSu(context, "-c", command);
}
}
} catch(Exception e) {
// Oops! Something went wrong, so we throw the exception here.
throw e;
}
}
To check if the mobile network is enabled or not:
private static boolean isMobileDataEnabledFromLollipop(Context context) {
boolean state = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
state = Settings.Global.getInt(context.getContentResolver(), "mobile_data", 0) == 1;
}
return state;
}
To get the value of the TRANSACTION_setDataEnabled field (borrowed from PhongLe's solution below):
private static String getTransactionCode(Context context) throws Exception {
try {
final TelephonyManager mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
final Class<?> mTelephonyClass = Class.forName(mTelephonyManager.getClass().getName());
final Method mTelephonyMethod = mTelephonyClass.getDeclaredMethod("getITelephony");
mTelephonyMethod.setAccessible(true);
final Object mTelephonyStub = mTelephonyMethod.invoke(mTelephonyManager);
final Class<?> mTelephonyStubClass = Class.forName(mTelephonyStub.getClass().getName());
final Class<?> mClass = mTelephonyStubClass.getDeclaringClass();
final Field field = mClass.getDeclaredField("TRANSACTION_setDataEnabled");
field.setAccessible(true);
return String.valueOf(field.getInt(null));
} catch (Exception e) {
// The "TRANSACTION_setDataEnabled" field is not available,
// or named differently in the current API level, so we throw
// an exception and inform users that the method is not available.
throw e;
}
}
To execute command via su:
private static void executeCommandViaSu(Context context, String option, String command) {
boolean success = false;
String su = "su";
for (int i=0; i < 3; i++) {
// Default "su" command executed successfully, then quit.
if (success) {
break;
}
// Else, execute other "su" commands.
if (i == 1) {
su = "/system/xbin/su";
} else if (i == 2) {
su = "/system/bin/su";
}
try {
// Execute command as "su".
Runtime.getRuntime().exec(new String[]{su, option, command});
} catch (IOException e) {
success = false;
// Oops! Cannot execute `su` for some reason.
// Log error here.
} finally {
success = true;
}
}
}
Hope this update clears up any misconception, misunderstanding, or question you may have about switching mobile network on/off on rooted Android 5+ devices.
Just to share a few more insights and possible solution (for rooted devices and system apps).
Solution #1
It seems like the setMobileDataEnabled method no longer exists in ConnectivityManager and this functionality was moved to TelephonyManager with two methods getDataEnabled and setDataEnabled.
I tried calling these methods with reflection as you can see in the code below:
public void setMobileDataState(boolean mobileDataEnabled)
{
try
{
TelephonyManager telephonyService = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
Method setMobileDataEnabledMethod = telephonyService.getClass().getDeclaredMethod("setDataEnabled", boolean.class);
if (null != setMobileDataEnabledMethod)
{
setMobileDataEnabledMethod.invoke(telephonyService, mobileDataEnabled);
}
}
catch (Exception ex)
{
Log.e(TAG, "Error setting mobile data state", ex);
}
}
public boolean getMobileDataState()
{
try
{
TelephonyManager telephonyService = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
Method getMobileDataEnabledMethod = telephonyService.getClass().getDeclaredMethod("getDataEnabled");
if (null != getMobileDataEnabledMethod)
{
boolean mobileDataEnabled = (Boolean) getMobileDataEnabledMethod.invoke(telephonyService);
return mobileDataEnabled;
}
}
catch (Exception ex)
{
Log.e(TAG, "Error getting mobile data state", ex);
}
return false;
}
When executing the code you get a SecurityException stating that Neither user 10089 nor current process has android.permission.MODIFY_PHONE_STATE.
So, yes this is an intended change to the internal API and is no longer available to apps that used that hack in previous versions.
(start rant: that dreadful android.permission.MODIFY_PHONE_STATE permission... end rant).
The good news are that in case you are building an app that can acquire the MODIFY_PHONE_STATE permission (only system apps can use that), you can use the above code to toggle mobile data state.
Solution #2
To check for current state of mobile data you can use the mobile_data field of Settings.Global (not documented in official documentation).
Settings.Global.getInt(contentResolver, "mobile_data");
And to enable/disable mobile data you can use shell commands on rooted devices (Just basic testing performed so any feedback in comments is appreciated).
You can run the following command(s) as root (1=enable, 0=disable):
settings put global mobile_data 1
settings put global mobile_data 0
I noticed that the service call method posted by ChuongPham does not work consistently on all devices.
I have found the following solution which, I think, will work without any issue on all ROOTED devices.
Execute the following via su
To enable mobile data
svc data enable
To disable mobile data
svc data disable
I think this is the simplest and best method.
Edit:
2 downvotes were for what I believe to be commercial reasons. The person has deleted his comment now. Try it yourself, it works!
Also confirmed to work by guys in comments.
I don't have enough reputation to comment but I have tried all the answers and found the following:
ChuongPham: Instead of using 83, I used reflection to get the value of the variable TRANSACTION_setDataEnabled from the com.android.internal.telephony.ITelephony so it works across all Android 5+ devices, regardless of brands.
Muzikant: Work if the app is moved to /system/priv-app/ directory (thanks to rgruet.) Else, it works via root, too! You just need to inform your users that the app will need a reboot before the changes to the mobile network will take place.
AJ: Work- sort of. Does not turn off subscription service so the devices I tested drained their batteries a fair bit. AJ's solution is NOT equivalent to Muzikant's solution despite the claim. I can confirm this by debugging different Samsung, Sony, and LG stock ROMs (I'm thorough) and can disprove AJ's claim that his solution is the same as Muzikant's. (Note: I can't get my hands on some Nexus and Motorola ROMs so haven't tested these ROMs with the proposed solutions.)
Anyway, hope it clears up any doubt over the solutions.
Happy coding!
PL, Germany
UPDATE: For those wondering how to get the value of the TRANSACTION_setDataEnabled field via reflection, you can do the following:
private static String getTransactionCodeFromApi20(Context context) throws Exception {
try {
final TelephonyManager mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
final Class<?> mTelephonyClass = Class.forName(mTelephonyManager.getClass().getName());
final Method mTelephonyMethod = mTelephonyClass.getDeclaredMethod("getITelephony");
mTelephonyMethod.setAccessible(true);
final Object mTelephonyStub = mTelephonyMethod.invoke(mTelephonyManager);
final Class<?> mTelephonyStubClass = Class.forName(mTelephonyStub.getClass().getName());
final Class<?> mClass = mTelephonyStubClass.getDeclaringClass();
final Field field = mClass.getDeclaredField("TRANSACTION_setDataEnabled");
field.setAccessible(true);
return String.valueOf(field.getInt(null));
} catch (Exception e) {
// The "TRANSACTION_setDataEnabled" field is not available,
// or named differently in the current API level, so we throw
// an exception and inform users that the method is not available.
throw e;
}
}
I found that su -c 'service call phone 83 i32 1' solution is most reliable for rooted devices. Thanks to Phong Le reference I have improved it by getting vendor/os specific transaction code using reflection. Maybe it will be useful for someone else. So, here is source code:
public void changeConnection(boolean enable) {
try{
StringBuilder command = new StringBuilder();
command.append("su -c ");
command.append("service call phone ");
command.append(getTransactionCode() + " ");
if (Build.VERSION.SDK_INT >= 22) {
SubscriptionManager manager = SubscriptionManager.from(context);
int id = 0;
if (manager.getActiveSubscriptionInfoCount() > 0)
id = manager.getActiveSubscriptionInfoList().get(0).getSubscriptionId();
command.append("i32 ");
command.append(String.valueOf(id) + " ");
}
command.append("i32 ");
command.append(enable?"1":"0");
command.append("\n");
Runtime.getRuntime().exec(command.toString());
}catch(IOException e){
...
}
}
private String getTransactionCode() {
try {
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
Class telephonyManagerClass = Class.forName(telephonyManager.getClass().getName());
Method getITelephonyMethod = telephonyManagerClass.getDeclaredMethod("getITelephony");
getITelephonyMethod.setAccessible(true);
Object ITelephonyStub = getITelephonyMethod.invoke(telephonyManager);
Class ITelephonyClass = Class.forName(ITelephonyStub.getClass().getName());
Class stub = ITelephonyClass.getDeclaringClass();
Field field = stub.getDeclaredField("TRANSACTION_setDataEnabled");
field.setAccessible(true);
return String.valueOf(field.getInt(null));
} catch (Exception e) {
if (Build.VERSION.SDK_INT >= 22)
return "86";
else if (Build.VERSION.SDK_INT == 21)
return "83";
}
return "";
}
Update:
Some of my users report that they have problem with turning ON mobile network via this method (turning off works correct). Does anyone have solution?
Update2:
After some digging the Android 5.1 code I've found that they changed signature of transaction. Android 5.1 brings official support of multi-SIM. So, the transaction need so-called Subscription Id as first parameter (read more here). The result of this situation is that the command su -c 'service call phone 83 i32 1' doesn't turn on Mobile Net on Android 5.1. So, the full command on Android 5.1 should be like this su -c 'service call phone 83 i32 0 i32 1' (the i32 0 is the subId, the i32 1 is command 0 - off and 1 - on). I've update the code above with this fix.
Solution #1 from Muzikant seems to work if you make the app "system" by moving the .apk to the /system/priv-app/ folder, not to the /system/app/ one (#jaumard: maybe that's why your test didn't work).
When the .apk is in the /system/priv-app/ folder, it can successfully request the dreadful android.permission.MODIFY_PHONE_STATE permission in the Manifest and call TelephonyManager.setDataEnabled and TelephonyManager.getDataEnabled.
At least that works on Nexus 5/ Android 5.0. The .apk perms are 0144. You need to reboot the device for the change to be taken into account, maybe this could be avoided - see this thread.
I derived final code from #ChuongPham and #A.J. for enable and disable cellular data. for enable you can call setMobileDataEnabled(true); and for disable you can call setMobileDataEnabled(false);
public void setMobileDataEnabled(boolean enableOrDisable) throws Exception {
String command = null;
if (enableOrDisable) {
command = "svc data enable";
} else {
command = "svc data disable";
}
executeCommandViaSu(mContext, "-c", command);
}
private static void executeCommandViaSu(Context context, String option, String command) {
boolean success = false;
String su = "su";
for (int i = 0; i < 3; i++) {
// Default "su" command executed successfully, then quit.
if (success) {
break;
}
// Else, execute other "su" commands.
if (i == 1) {
su = "/system/xbin/su";
} else if (i == 2) {
su = "/system/bin/su";
}
try {
// Execute command as "su".
Runtime.getRuntime().exec(new String[]{su, option, command});
} catch (IOException e) {
success = false;
// Oops! Cannot execute `su` for some reason.
// Log error here.
} finally {
success = true;
}
}
}
Not all phones and versions of android have things Enable/disable mobile data the same.
otherwise, this solution is tested on my phone (SAMSUNG SM-J100H)
To enable mobile data :
adb shell service call phone 27
To disable mobile data :
adb shell service call phone 28
To correct Muzikant Solution #2
settings put global mobile_data 1
Does enable only the toggle for mobile data but does nothing to the connectivity. Only the toggle is enabled. In order to get the data working using
su -c am broadcast -a android.intent.action.ANY_DATA_STATE --ez state 1
Gives error as the extra for
android.intent.action.ANY_DATA_STATE
Requires String Object while --ez parameter is used for boolean. Ref: PhoneGlobals.java & PhoneConstants.java. After using connecting or connected as extra using command
su -c am broadcast -a android.intent.action.ANY_DATA_STATE --es state connecting
Still doesnt do anything to enable the data.
The following solution works by enabling and disabling mobile data (as you would by clicking on the slider "Mobile data"). Requires root. Tested on LineageOS 16.0 (rooted):
Steps 1-3 are taken from the accepted answer at Turning off second SIM via adb shell/Tasker - using activities/intents:
Download jadx from https://github.com/skylot/jadx
ADB pull the devices framework.jar (adb pull /system/framework/framework.jar)
Open the .jar file with 7-Zip and extract the *.dex files. Open each .dex file with jadx-gui until you find the one with the following tree: com.android.internal.telephony.ITelephony
Find TRANSACTION_enableDataConnectivity and TRANSACTION_disableDataConnectivity, for me these are 38 and 39 respectively
From a root shell (e.g., adb shell or Termux), run service call phone 38 to enable data, and service call phone 39 to disable data.
I have a small issue regarding Ethernet.
My three questions are:
Can we programmatically Turn-On/Off Ethernet?
Can we programmatically Enable/Disable Ethernet?
Can we programmatically Connect Ethernet?
The above Questions are done with the Wifi. Like
We can programmatically Turn-On/Off Wifi.
We can programmatically Enable/Disable Wifi.
We can programmatically Connect Wifi using WifiManager.
Does android provides any EthernetManager like as WifiManager to handle Ethernet?
Or, if this doesn't seem feasible, then my original requirement is:
The first thing I am going to clear is "DEVICE IS ROOTED" .
Can I manipulate the Settings (Default)? Like I don't want any other option in the Settings.apk other than WIFI and Ethernet. It should show only Wifi and Ethernet. That's it. Can I disable all the options from the Settings or Can I remove all the other options from the Settings?
The solution I will present here is a hack using reflection and does only work on a rooted android system.
Your device might have the popular android.net.ethernet package. In an Activity, try
Object emInstance = getSystemService("ethernet");
It returns an valid instance of the EthernetManager or null. Null means you are out of luck.
An additional requirement might be depending on your device: Ethernet and Wifi might only work exclusively. You might need to disable Wifi to enable Ethernet and vice versa.
To enable Ethernet by reflection use your instance of the EthernetManager.
The method you want to invoke is setEthEnabled(boolean enabled)
Class<?> emClass = null;
try {
emClass = Class.forName("android.net.ethernet.EthernetManager");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Object emInstance = getSystemService("ethernet");
Method methodSetEthEnabled = null;
try {
methodSetEthEnabled = emClass.getMethod("setEthEnabled", Boolean.TYPE);
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
methodSetEthEnabled.setAccessible(true);
try {
// new Boolean(true) to enable, new Boolean(false) to disable
methodSetEthEnabled.invoke(emInstance, new Boolean(false));
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Your application manifest needs these permissions
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
The permission WRITE_SECURE_SETTINGS can only be acquired by system apps. The app does not need to be signed by a system key. It can be any valid sign (like the regular Android App Export function). Use busybox to remount the system partition for write access and move your apk into the /system/app folder. Reboot the device and it should work.
Can we programmatically Connect Ethernet ?
There is no Access Point to connect you like with Wifi. You either configure it for DHCP or provide static values. This can of course also be done via reflection.
You will need the class EthernetDevInfo for that.
The actual implementation of the EthernetManager and EthernetDevInfo might slightly differ between Android versions and devices as it doesn't have to conform to a public api (yet) and might even be a custom version.
To get a list of getters and setters you can use a Introspector or reflection in general.
Ok here are some methods i made for manipulating with the ETHERNET INTERFACE (eth0).
1) A method for checking if an ethernet interface exists
public static boolean doesEthExist() {
List<String> list = getListOfNetworkInterfaces();
return list.contains("eth0");
}
public static List<String> getListOfNetworkInterfaces() {
List<String> list = new ArrayList<String>();
Enumeration<NetworkInterface> nets;
try {
nets = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
e.printStackTrace();
return null;
}
for (NetworkInterface netint : Collections.list(nets)) {
list.add(netint.getName());
}
return list;
}
2) A method for checking if the ETHERNET is enabled or ON
public static boolean isEthOn() {
try {
String line;
boolean r = false;
Process p = Runtime.getRuntime().exec("netcfg");
BufferedReader input = new BufferedReader (new InputStreamReader(p.getInputStream()));
while ((line = input.readLine()) != null) {
if(line.contains("eth0")){
if(line.contains("UP")){
r=true;
}
else{
r=false;
}
}
}
input.close();
Log.e("OLE","isEthOn: "+r);
return r;
} catch (IOException e) {
Log.e("OLE","Runtime Error: "+e.getMessage());
e.printStackTrace();
return false;
}
}
3) A method for enabling or disabling the Ethernet depending on the state in which it is
public static void turnEthOnOrOff() {
try {
if(isEthOn()){
Runtime.getRuntime().exec("ifconfig eth0 down");
}
else{
Runtime.getRuntime().exec("ifconfig eth0 up");
}
} catch (IOException e) {
Log.e("OLE","Runtime Error: "+e.getMessage());
e.printStackTrace();
}
}
4) A method for connecting via ethernet depending on the chosen type (dhcp/static)
private boolean connectToStaticSettingsViaIfconfig(StaticConnectionSettings scs) {
try {
if(typeChosen.equalsIgnoreCase("dhcp")){
Runtime.getRuntime().exec("ifconfig eth0 dhcp start");
}
else{
Runtime.getRuntime().exec("ifconfig eth0 "+scs.getIp()+" netmask "+scs.getNetmask()+" gw "+scs.getGateway());
}
} catch (IOException e) {
Log.e("OLE","Runtime Error: "+e.getMessage());
e.printStackTrace();
return false;
}
return true;
}
There is one more class which i created for storing all the eth values needed. This class is than initialized with the values the user inserts.
public class StaticConnectionSettings {
private String ip, netmask, dns, mac, gateway, type;
//Getters and Setters
}
This is it ... I will test it shortly... This code lacks a test phase (ping). And maybe it needs setting of DNS. But this can be done easily. I have not included it because i think on our device it will work also without the DNS setting.
It works for Android 6.0.1
Class<?> ethernetManagerClass = Class.forName("android.net.ethernet.EthernetManager");
Method methodGetInstance = ethernetManagerClass.getMethod("getInstance");
Object ethernetManagerObject = methodGetInstance.invoke(ethernetManagerClass);
Method methodSetEthEnabled = ethernetManagerClass.getMethod("setEthernetEnabled", Boolean.TYPE);
methodSetEthEnabled.invoke(ethernetManagerObject, isEnabled);
Three Answeres to your above questions:
Yes. You could try using ifconfig eth0 down ; ifconfig eth0 up. But i have not tested it by myself yet.
Yes, but you do not have to. Android does the switching for you. If you connect to WiFi, Ethernet disables. If you are already connected to WiFi and you plug your ethernet cable into the device; you need only to disable WiFi (which you know how to) and android switches automatically to ethernet.
Not so easy as you may think. I have the same problem and until now i have found only one solution which i have not yet tested. Since android runs on the linux kernel, we can use ifconfig in order to manipulate the ethernet connection.
An explanation is hidden here:
http://elinux.org/images/9/98/Dive_Into_Android_Networking-_Adding_Ethernet_Connectivity.pdf
And the youtube video of this lecture
http://www.youtube.com/watch?v=LwI2NBq7BWM
And a reference on how to use ifconfig for android
Android ethernet configure IP using dhcp
So if you come to a possible solution, please share it!! If i will do it before you i will certenly.
I've read many questions here on SO that ask how to enable USB tethering programmatically.
The answer is always the same, ordinary applications can't do it, only system apps.
Yet for 2.3 you could download an app in the market that would do it for you.
https://play.google.com/store/apps/details?id=org.tdtran.autousbtethering
On ICS (Android 4.0.3) it no longer works.
How did they do it for 2.3? Is it possible also for 4.0?
using the following code you can enable USB tethering. i didt test in 4.0.
public void switchOnTethering() {
Object obj = getSystemService(Context.CONNECTIVITY_SERVICE);
for (Method m : obj.getClass().getDeclaredMethods()) {
if (m.getName().equals("tether")) {
try {
m.invoke(obj, "usb0");
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
ICS and above: To execute the reflection method, the application would require the WRITE_SECURE_SETTINGS permission.
This is not available unless the phone is rooted.
since this is one of the most popular pages in the Google results for this topic I'd like to contribute my code which is checking the available interfaces. It does work on a Gingerbread phone I have, but not my Galaxy S3.
// DETECT INTERFACE NAME
Log.i("UsbTethering","Detecting tetherable usb interface.");
String[] available = null;
ConnectivityManager connMgr = (ConnectivityManager)connectivityServiceObject;
Method[] wmMethods = connMgr.getClass().getDeclaredMethods();
for(Method getMethod: wmMethods)
{
if(getMethod.getName().equals("getTetherableUsbRegexs"))
{
try
{
available = (String[]) getMethod.invoke(connMgr);
break;
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
// DETECT INTERFACE NAME
if(available.length > 0)
{
for(String interfaceName : available)
{
Log.i("UsbTethering", "Detected " + String.valueOf(available.length) + " tetherable usb interfaces.");
Log.i("UsbTethering", "Trying to " + desiredString + " UsbTethering on interface " + interfaceName + "...");
Integer returnCode = (Integer)method.invoke(connectivityServiceObject, interfaceName);
if(returnCode == 0)
{
Log.i("UsbTethering", "UsbTethering " + desiredString + "d.");
return true;
}
else
{
Log.w("UsbTethering", "Failed to " + desiredString + "Usb Tethering. ReturnCode of method " + method.getName() + ": " + String.valueOf(returnCode));
}
}
}
On Samsumg Galaxy Tab 2 10.1 the interface isn't called "usb0" but "rndis0". Maybe that's the same for Galaxy SII
The port "rndis0" is enabled dynamically not availble in the the Tetherable interfaces list.
It gets added when the user selects the USB Tethering option in the menu.
The function ConnectivityManager::setUsbTethering(bool) is called when the option is selected. And this function call is allowed only for System applications.
I know its an old thread but i hope this could help some other people in the future ,
That code worked for me in Android 4.4(with root privilege)
code:
the trick is to use reflection , the method which changes the usb tethring is called "setUsbTethering"
I wont write the entire class but here is what you need:
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
Method usbTethering = connectivityManager.getClass().getMethod('setUsbTethering')
int returnCode = (Integer)usbTethering.invoke(connectivityManager, true);
0 = success
you can print the entire class methods using the following code
private static void printClassMethod(#NonNull Class aClazz) {
Method[] wmMethods = aClazz.getDeclaredMethods();
for (Method method : wmMethods) {
Log.i('anytag', method.getName());
}
}
then call printClassMethod(ConnectivityManager.class)
More Over, you can go onto the class itself and check the methods and arguments needed.
to get things work:
you must set your app as System app,
Declare in manifest : android:sharedUserId="android.uid.system"
add writing secure permission<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"
Sign the apk using google certificate key(apk-with-system-privileges) and push it to /system/app
Mind, that usbTethering is turned on while USB is attached, so a nicer solution will be to register to both USB_STATE and USB_ATTACH and enable/disable it correspondingly in onReceive Method. (USB tethering is turned off automatically when USB is de-attached)
I want to simulate the action of going throgh Settings->Wireless->Bluetooth and connect a paired bluetooth headset programmatically. I've done some search in Stackoverflow and Google, both indicating that there is no solution available before API level 11. However, I'm interested in working it out by peeking in the source code of bluetooth implementation of Android. The problem is that I'm not aware of which specific source code I should review. Any suggestions? Many thanks.
After days of struggling, I've now managed to do it, cheers :)
Add android.bluetooth.IBluetoothA2dp.aidl in your /src dir of your app;
Add this private method in your code:
private IBluetoothA2dp getIBluetoothA2dp() {
IBluetoothA2dp ibta = null;
try {
Class c2 = Class.forName("android.os.ServiceManager");
Method m2 = c2.getDeclaredMethod("getService", String.class);
IBinder b = (IBinder) m2.invoke(null, "bluetooth_a2dp");
Log.d("Felix", "Test2: " + b.getInterfaceDescriptor());
Class c3 = Class.forName("android.bluetooth.IBluetoothA2dp");
Class[] s2 = c3.getDeclaredClasses();
Class c = s2[0];
// printMethods(c);
Method m = c.getDeclaredMethod("asInterface", IBinder.class);
m.setAccessible(true);
ibta = (IBluetoothA2dp) m.invoke(null, b);
} catch (Exception e) {
Log.e("flowlab", "Erroraco!!! " + e.getMessage());
}
Test it with this:
private void testBluetoothA2dp(BluetoothDevice device) {
// TODO Auto-generated method stub
// TODO Auto-generated method stub
IBluetoothA2dp ibta = getIBluetoothA2dp();
try {
Log.d("Felix", "Here: " + ibta.getSinkPriority(device));
ibta.connectSink(device);
} catch (RemoteException e) {
// * TODO Auto-generated catch block
e.printStackTrace();
}
}
I'm not able to provide references of these code since I've spent lots of time googling, checking out stackoverflow, and reviewing Android source code, but failed to keep track of sources. Many thanks to you guys in Stackoverflow :)
OK, I got this updated to support Honeycomb and up. You need to add new functions to the interface. I did that here:
interface IBluetoothA2dp {
boolean connectSink(in BluetoothDevice device); // Pre API 11 only
boolean disconnectSink(in BluetoothDevice device); // Pre API 11 only
boolean connect(in BluetoothDevice device); // API 11 and up only
boolean disconnect(in BluetoothDevice device); // API 11 and up only
boolean suspendSink(in BluetoothDevice device); // all
boolean resumeSink(in BluetoothDevice device); // all
BluetoothDevice[] getConnectedSinks(); // change to Set<> once AIDL supports, pre API 11 only
BluetoothDevice[] getNonDisconnectedSinks(); // change to Set<> once AIDL supports,
int getSinkState(in BluetoothDevice device);
boolean setSinkPriority(in BluetoothDevice device, int priority); // Pre API 11 only
boolean setPriority(in BluetoothDevice device, int priority); // API 11 and up only
int getPriority(in BluetoothDevice device); // API 11 and up only
int getSinkPriority(in BluetoothDevice device); // Pre API 11 only
boolean isA2dpPlaying(in BluetoothDevice device); // API 11 and up only
}
Then you need to check the API version before calling functions in this interface. Here is my example:
if (android.os.Build.VERSION.SDK_INT < 11) {
IBluetoothA2dp ibta = getIBluetoothA2dp();
try {
Log.d(LOG_TAG, "Here: " + ibta.getSinkPriority(device));
if (ibta != null)
ibta.connectSink(device);
} catch (Exception e) {
Log.e(LOG_TAG, "Error " + e.getMessage());
}
} else {
IBluetoothA2dp ibta = getIBluetoothA2dp();
try {
Log.d(LOG_TAG, "Here: " + ibta.getPriority(device));
if (ibta != null)
ibta.connect(device);
} catch (Exception e) {
Log.e(LOG_TAG, "Error " + e.getMessage());
}
}
Hope this helps. I was able to get the same app to work with both interfaces.
I tried this on Android 4.2
and following line returns null.
It was working on 4.1, any ideas ?
IBinder b = (IBinder) m2.invoke(null, "bluetooth_a2dp");
this is KIND of related to the connect/reconnect issue (the ANSWER being an apple script) ..
i just bought an android htc one V and am using it as a hotspot via the app PdaNet (installed both on my phone and my mac os 10.5.8 ppc laptop).
I can't seem to get the hotspot tethering working via wifi or usb but it DOES work well with BLUETOOTH! only problem is the connection only lasts up to 2 minutes to 40 minutes (looking at it now ha, a record) and i have to reconnect manually, which only takes 2 seconds but it'd be nice if the Network aspect of my mac could AUTO reconnect.
my cell is not the problem as it's sending out a constant signal (although i could be losing signal temporarily from my cell, which is normal connection stuff) .. the problem is having my laptop auto RE-connect. my laptop and htc one v DO remain paired, there's just no AUTO-RECONNECT on the laptop end.
i wish i knew apple scripts bcs then i could maybe write an apple script that AUTO-RECONNECTS a lost bluetooth connection .. or maybe a widget could do this? if so i'd have it made in the shade as bluetooth tethering is working NICELY.
i was hoping this would help both myself and others searching for the same answers .. if you keep this thread open i could return later with some possible apple script solutions (which i will have to quickly learn) .. thanks -marcus