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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With