The setMobileDataEnabled method can no longer be called for Android L or later

I registered Issue 78084 with Google regarding the setMobileDataEnabled() method, which can no longer be called via reflection. This was called from Android 2.1 (API 7) to Android 4.4 (API 19) via reflection, but with Android L and later, even using root, the setMobileDataEnabled() method cannot be called.

The official answer is that the problem is "Closed" and the status is set to "WorkingAsIntended." Simple Google explanation:

Private APIs are private because they are unstable and may disappear without notice.

Yes, Google, we are aware of the risk of using reflection to invoke a hidden method - even before Android appeared on the scene - but you need to provide a more solid answer to alternatives, if any, to achieve the same result as setMobileDataEnabled() . (If you are unsatisfied with Google’s decision, like me, log in to Issue 78084 and run it as much as possible to tell Google about the error of their path.)

So, my question is for you: are we at a standstill when it comes to programmatically enabling or disabling the mobile network feature on an Android device? This brutal approach from Google somehow does not suit me. If you have a workaround for Android 5.0 (Lollipop) and above, I would like to hear your answer / discussion in this thread.

I used the code below to find out 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 this is not so.

UPDATE It is currently possible to switch the mobile network if the device is rooted. However, for non-root devices, it is still an investigative process, since there is no universal method for switching a mobile network.

+47
android reflection android-5.0-lollipop
Oct 23 '14 at 23:57
source share
7 answers

To expand Muzikant Solution # 2, try solving the solution below on a shortened Android 5.0 device (since I don’t have one at the moment) and let me know if it works or doesn’t 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 variable mobile_data can be found in the Android API 21 source code in /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"; 

So far, android.intent.action.ANY_DATA_STATE Intent can be found in the Android API 21 source code in /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 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 do not want to implement the above Java codes in your Android application, you can run su commands using the shell (Linux) or the command line (Windows) as follows:

 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 in the /android-sdk/platform-tools/ directory. The settings command is only supported on Android 4.2 or later. The old version of Android will report a "sh: settings: not found" error.

UPDATE 2 . Another way to switch your mobile network to the Android 5+ root device is to use the undocumented shell service command. The following command can be executed via ADB to switch the mobile network:

 // 1: Enable; 0: Disable adb shell "su -c 'service call phone 83 i32 1'" 

Or simply:

 // 1: Enable; 0: Disable adb shell service call phone 83 i32 1 

Note 1 Transaction code 83 used in the service call phone command may change between versions of Android. Please check the com.android.internal.telephony.ITelephony field value TRANSACTION_setDataEnabled for your version of Android. Also, instead of hardcoding 83, you'd better use Reflection to get the value of the TRANSACTION_setDataEnabled field. Thus, it will work in all mobile brands running Android 5+ (if you do not know how to use Reflection to get the value of the TRANSACTION_setDataEnabled field, see the Solution from PhongLe below - save me from duplication here). It is important . Please note that transaction code TRANSACTION_setDataEnabled was only introduced in Android 5.0 and later. Running this transaction code in earlier versions of Android will do nothing, since the transaction code TRANSACTION_setDataEnabled does not exist.

Note 2 : adb is located in the /android-sdk/platform-tools/ directory. If you do not want to use ADB, execute the method using su in your application.

Note 3 See UPDATE 3 below.

UPDATE 3 . Many Android developers emailed me questions about turning on / off the mobile network for Android 5+, but instead of answering individual emails, I will send my answer here so that everyone can use it and adapt it for their Android applications.

First of all, let me clarify some misconceptions and misunderstandings regarding:

 svc data enable svc data disable 

The above methods will turn on / off background data input, not a subscription service, so the battery discharges a fair bit, because the subscription service - the Android system service - will still work in the background. For Android devices that support multiple SIM cards, this scenario is worse, since the subscription service constantly checks for available mobile networks (networks) for use with active SIM cards available on the Android device. Use this method at your own risk.

Now the correct way to disable the mobile network, including the corresponding subscription service, is using the SubscriptionManager class, introduced in API 22:

 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 ie 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 on 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 the PhongLe 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 a 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; } } } 

We hope that this update will clear up any misconception, misunderstanding or question that you may have about enabling / disabling the mobile network on Android 5+ root devices.

+25
Nov 29 '14 at 4:38
source share

I noticed that the service invocation method sent by ChuongPham does not work consistently on all devices.

I found the following solution, which I think will work without any problems on all ROOTED devices.

Do the following via su

To enable mobile data

 svc data enable 

To disable mobile data

 svc data disable 

I think this is the easiest and best method.

Edit: 2 downvotes were for what I consider commercial reasons. Man deleted his comment now. Try it yourself, it works! It’s also confirmed that the guys in the comments work.

+9
Feb 23 '15 at 21:34
source share

Just to share a few more ideas and a possible solution (for embedded devices and system applications).

Decision No. 1

It seems that the setMobileDataEnabled method no longer exists in ConnectivityManager , and this functionality has been ported to TelephonyManager two ways 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 you execute the code, you get a SecurityException message that says Neither user 10089 nor current process has android.permission.MODIFY_PHONE_STATE.

So, yes, this is a planned change in the internal API and is no longer available for applications that used this hack in previous versions.

(start of conversation: this is a terrible android.permission.MODIFY_PHONE_STATE permission ... end rant).

The good news is that if you create an application that can get MODIFY_PHONE_STATE permission (only system applications can use it), you can use the code above to switch the state of mobile data.

Decision number 2

To check the current state of mobile data, you can use the mobile_data Settings.Global field (not documented in the official documentation).

 Settings.Global.getInt(contentResolver, "mobile_data"); 

And to enable / disable mobile data, you can use shell commands on root devices (simple basic testing is done, so any feedback in the comments is appreciated). You can run the following commands: root (1 = enable, 0 = disable):

 settings put global mobile_data 1 settings put global mobile_data 0 
+6
Nov 19 '14 at 7:56
source share

I don't have enough reputation for comments, but I tried all the answers and found the following:

ChuongPham: instead of using 83, I used reflection to get the value of the TRANSACTION_setDataEnabled variable from com.android.internal.telephony.ITelephony , so it works on all Android 5+ devices, regardless of brand.

Muzikant: work if the application is moved to the /system/priv-app/ directory (thanks rgruet.) Else, it also works as root! You just need to tell your users that the application will need to be rebooted before changes occur in the mobile network.

AJ: Working view. It doesn’t disable the subscription service, so the devices I tested have run out of batteries. The AJ solution is NOT equivalent to the Muzikant solution, despite the requirement. I can confirm this by debugging various spare drives from Samsung, Sony and LG (I completely), and I can refute AJ's claim that his solution is the same as Muzikant's. (Note: I cannot access some Nexus and Motorola devices, so I have not tested these ROMs with the proposed solutions.)

In any case, hope it resolves any doubts about decisions.

Happy coding! PL, Germany

UPDATE For those who are wondering how to get the value of the TRANSACTION_setDataEnabled field through 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; } } 
+3
Apr 11 '15 at 2:50
source share

I found that the su -c 'service call phone 83 i32 1' is the most reliable for root devices. Thanks to the Phong Le link, I improved it by getting the vendor / os transaction code using reflection. Maybe it will be useful for someone else. So here is the 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 a problem with turning on the mobile network using this method (disconnecting works correctly). Does anyone have a solution?

Update2:

After digging some Android 5.1 code, I found that they changed the transaction signature. Android 5.1 supports official multi-SIM support. Thus, the transaction requires the so-called Subscription ID as the first parameter ( read on here ). The result of this situation is that the su -c 'service call phone 83 i32 1' does not enable Mobile Net on Android 5.1. So, the full command on Android 5.1 should look something like this: su -c 'service call phone 83 i32 0 i32 1' ( i32 0 is subId, i32 1 is the command 0 - off and 1 - on). I updated the code above with this fix.

+3
Apr 18 '15 at 21:12
source share

Muzikant's solution # 1 seems to work if you create an “application system” by moving .apk to the /system/priv-app/ folder, not to /system/app/ one (@jaumard: maybe why your test isn’t worked).

When .apk is in the /system/priv-app/ folder, it can successfully request the awful permission android.permission.MODIFY_PHONE_STATE in the manifest and call TelephonyManager.setDataEnabled and TelephonyManager.getDataEnabled .

At least it works on Nexus 5 / Android 5.0. Variables .apk 0144 . You need to reboot the device to make changes, perhaps this could have been avoided - see this thread .

+2
Dec 08 '14 at 10:10
source share

To fix Muzikant Solution No. 2

 settings put global mobile_data 1 

It only includes switching for mobile data, but does nothing to connect. Only the switch is on. To get data using

 su -c am broadcast -a android.intent.action.ANY_DATA_STATE --ez state 1 

Gives an error as additional for

 android.intent.action.ANY_DATA_STATE 

A String object is required, while the --ez option is used for booleans. Link: PhoneGlobals.java and PhoneConstants.java. After using a connection or connecting with the command

 su -c am broadcast -a android.intent.action.ANY_DATA_STATE --es state connecting 

Still not doing anything to include data.

+1
Dec 24 '14 at 19:21
source share



All Articles