49

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.

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
ChuongPham
  • 4,761
  • 8
  • 43
  • 53
  • 3
    @CommonsWare: Doubted it is a rant as the OP has included codes to demonstrate what he/she is trying to do. Plus the supposedly rant comments are legit since it outline the root cause of the issue logged with Google and their lemming response to an otherwise valid question. –  Oct 24 '14 at 00:09
  • 16
    The problem is Google *doesn't want* any app to be able to enable/disable data programmatically. That's why there's no API for it and that's why they've closed the reflection loophole. I wish they would just make it a permission. What would be wrong with that? If anyone has a solution to at least accomplish this with root, please post it. – Flyview Oct 24 '14 at 02:25
  • 1
    You are not supposed to call internal APIs, so there's nothing to complain about. Also, generally apps should be able to affect other apps/user on the device, so this is a good thing. Do you really want some random app to turn on mobile data and download 10GB in the background? Tell us why you think you need to toggle mobile data in the first place, and you might get a more constructive answer. – Nikolay Elenkov Nov 07 '14 at 02:14
  • 5
    I'm aware of the implication of using Reflection - and if Reflection was not necessary, I doubted that Sun had created it in the first place. So, instead of providing a public method for us to use, Android/Java programmers have resorted to using Reflection to enable/disable mobile data. One useful scenario would be an Android app - with the user's knowledge defined via the app's preferences - can automatically turn off mobile data, or other types of network, at a certain time interval e.g. when the user go to bed at night and don't want radio waves cooking their brain. – ChuongPham Nov 08 '14 at 08:43
  • 8
    The above is just one of many scenarios where an Android app can help users control what networks are on or off. So, instead of addressing the shortcoming of the semi-static Android security model in Android 5.0 (Lollipop), Google has opted instead to woo users with candy-eyed Material Design which IMHO is less of a must-have than it is to fix the security model so the users have the final control in terms of what apps can do on their mobile devices. Fix the security model and rogue apps will no longer be a problem - while legit apps can help users in many ways through innovative design. – ChuongPham Nov 08 '14 at 08:54
  • you can look at solution i've found here [link](http://stackoverflow.com/questions/25466384/disable-enable-mobile-data-on-android-l-with-root-acces) – Slv3r Jan 13 '15 at 11:01
  • What is the working method for rooted phones? @Slv3r, the service call method is not working on my device (lg). I have posted an answer below which uses svc, it is woking on all three devices I tested it with (lg and samsung) but it has 2 downvotes. Can anybody please explain what's wrong with this method? – A.J. Feb 26 '15 at 13:29
  • Please see, its a only solution. – Arsalan Khan Sep 22 '17 at 09:42

10 Answers10

26

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.

ChuongPham
  • 4,761
  • 8
  • 43
  • 53
  • pls send an APK or tell me how and where to run the su command. I have a rooted n4 running 5.0 – desidigitalnomad Nov 29 '14 at 11:57
  • "Broadcast completed: result=0" . Seems to be working. Maybe Power Toggles app is also using this method. The apps widget can change mobile data state from home-screen itself – desidigitalnomad Nov 29 '14 at 12:43
  • nope not working data was not deactivate/activate but toggle button under phone settings was but I always have internet connexion. adb result : Broadcasting: Intent { act=android.intent.action.ANY_DATA_STATE (has extras) } Broadcast completed: result=0 – jaumard Nov 29 '14 at 12:59
  • 1
    @jaumard: Can you check `/data/data/com.android.providers.settings/databases/settings.db` database in your Android 5.x rooted device and let me know which table(s) contain the `mobile_data` field. There are three possible locations: `global`, `secure`, or `system` table. I just need to know where the `mobile_data` field is to determine if the `su` commands are executing at the correct location. Thanks. – ChuongPham Nov 29 '14 at 13:33
  • The command is correct cause toggle button on phone settings are toggled correctly I can make it change why your command and all other apps who show toggle state think data is off but it was not – jaumard Nov 29 '14 at 13:36
  • @jaumard is right, it just toggles the Cellular Data toggle button. "Power Toggles" app also seems to be trying to do the same. Though it changes the toggle button state, mobile data is not enabled. "Et tu Google" – desidigitalnomad Nov 29 '14 at 18:15
  • I checked in the db you mentioned. The _mobile_data_ field is in the table **global**. – desidigitalnomad Nov 29 '14 at 18:21
  • @aaRBiyecH: Maybe you need to try this [app](https://play.google.com/store/apps/details?id=com.cygery.toggledata). It uses a similar solution to Muzikant's Solution #1 on rooted devices and it seems to work according to the users' reviews. –  Nov 30 '14 at 16:13
  • "state" is String so --es should be used in case. It accepts connecting connected disconnected as parameters. – Sahil Lombar Dec 24 '14 at 12:01
  • I found that the service code (83 above) is not same for all devices and that's the reason it was not working for me. The code for my device was 92. This means that we have to dynamically calculate the code on the device. I prefer using the svc method(posted as answer by me, ignore downvotes). It is simpler and works consistently on all devices. – A.J. Feb 28 '15 at 16:20
  • And what is the constant `i32`? Where did you found it? – nickkadrov Apr 26 '15 at 15:51
  • Will answer by myself. The i32 looks like data type specification. – nickkadrov Apr 26 '15 at 17:23
  • So the only way to achieve this currently requires root-access? – levi May 29 '15 at 00:11
  • Thank you, this is one of best answers here ever : ) – Michal Jun 22 '15 at 12:49
  • @Michal: No worries, mate. Glad I could help you! ;) – ChuongPham Jul 04 '15 at 12:50
  • @levi: Yes- enabling mobile network can only be done with root access. – ChuongPham Jul 09 '15 at 06:34
  • @ChuongPham What's the correct way to do it on pre-Lollipop? – android developer Jul 12 '15 at 19:35
  • @ChuongPham In isMobileDataEnabledFromLollipop(), I'd suggest to call the hidden API, ConnectivityManager.getMobileDataEnabled(). This API is still available on Android 5.x and more reliable than checking the value of "mobile_data". – Sam Lu Sep 10 '15 at 00:48
  • @ChuongPham hi, is it possible for not rooted device? or any other solution for lollipop and higher versions . – User Learning Apr 21 '16 at 08:14
  • The `executeCommandViaSu` algorithm is wrong since the `finally` block always set the `success` variable to `true`. One should move this assignment to the `try` block. – Xvolks Feb 03 '17 at 12:46
  • I am facing this exception : java.io.IOException: Error running exec(). Command: [su, -c, service call phone 86 i32 1] Working Directory: null Environment: null – Sai's Stack Jun 29 '17 at 10:37
  • @ChuongPham On Android P the method we want to target in ITelephony has changed from "setDataEnabled" to "setUserDataEnabled". Can you please update your answer? However it looks like we can't build for Android P or else these methods become totally inaccessible! Are we stuck at compileSDK 27? – Flyview Feb 05 '19 at 20:57
  • One more thing: I don't think it's necessary to loop through all subscriptions because there can only be one selected for data. You can use SubscriptionManager.getDefaultDataSubscriptionId() (or pre-N, reflection on SubscriptionManager's getDefaultDataSubId) to get the subId to use for setDataEnabled/setUserDataEnabled. – Flyview Feb 09 '19 at 21:10
10

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
Muzikant
  • 8,070
  • 5
  • 54
  • 88
  • 2
    Thanks for posting your solutions. For Solution #1, I guess you don't need to use Reflection to access the "setDataEnabled" method as you can directly use `TelephonyManager.setDataEnabled` and `TelephonyManager.getDataEnabled` if you've made your app a system app. – ChuongPham Nov 20 '14 at 01:45
  • None of the 2 solutions is working for me :( for lollipop even as system app a security exception is throw for solution 1 and solution two not working data doesn't toggle – jaumard Nov 26 '14 at 20:36
  • nope not working data was not deactivate/activate but toggle button under phone settings was but I always have internet connexion. adb result : Broadcasting: Intent { act=android.intent.action.ANY_DATA_STATE (has extras) } Broadcast completed: result=0 – jaumard Nov 29 '14 at 12:48
  • @jaumard: Maybe you need to try this [app](https://play.google.com/store/apps/details?id=com.cygery.toggledata). It uses a similar solution to Muzikant's Solution #1 on rooted devices and it seems to work according to the users' reviews. However, you have stated that Muzikant's Solution #1 did not work for you. I'm curious as to what's worked and what's not, since your comment and the app in Play Store contradict each other. –  Nov 30 '14 at 16:12
  • Following application is working, but I have no idea, how it does: http://forum.xda-developers.com/android/apps-games/widget-toggle-background-data-5-0xda-t2947813 – Michal Dec 07 '14 at 08:59
  • 2
    @Michal, please notice that the referred app toggles background data and not mobile data. Also, it requires root. – Muzikant Dec 07 '14 at 13:32
  • Solution 1 with root and by using this method to install as system app works for me with android 9PKQ11.190616.001 https://stackoverflow.com/questions/28302833/how-to-install-an-app-in-system-app-while-developing-from-android-studio – bormat Mar 31 '21 at 18:10
9

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.

A.J.
  • 1,520
  • 17
  • 22
  • Seriously... downvote? It turns off(and on) mobile data on all devices I tested it with - not background data, it turns off(and on) the mobile data connection. Mobile radio? We are not talking about switching the mobile radio on/off here, that's a different story. – A.J. Feb 25 '15 at 13:10
  • Tested on 2 more devices (total 5). It is working. Downvoters, please explain the reason. @CKD Publishing, run "svc help data" on the terminal and read the result. It's for mobile data connection, not background data. Please provide links. – A.J. Feb 26 '15 at 21:26
  • May i use it to update my answare on other question? – Slv3r Apr 03 '15 at 21:01
  • @Slv3r, you can simply add a link to this answer i.e. http://stackoverflow.com/a/28683889/1951805 – A.J. Apr 03 '15 at 21:07
  • @A.J.: If SO allows two downvotes for the same answer- as you have stated by "shouting" in bolded words- then 99.9% of us don't know about this "latest change" that SO administrators have applied to the site. Else, we would double-vote on other half-answers abundant on SO. Also, slandering someone by stating "claims his widget is the only one that works" is not only unprofessional but downright lies. If I could bring back my comment, I would. But SO does not allow resurrection of deleted comment... –  Apr 06 '15 at 09:27
  • @A.J.: ...I wanted to bring my deleted comment back to show everyone how you lied in order to get sympathy for your answer (I could request SO administrator to recreate my deleted comment, but I don't know if they can do it.) If your answer is good, then it should be able to stand on its own merit. Abusing someone is not going to help you in the long term. –  Apr 06 '15 at 09:28
  • @A.J.: Now, getting back to topic: I guess you saw the 3G/4G/H+ symbol disappeared after you ran "svc data disable" on the devices you've tested and think that you have successfully turned off mobile network. On the contrary, your answer is equivalent to turning off the "data" connectivity, so you won't receive any network traffic to a device, not the radio receiver receiving the signal. Of course, if the the receiver is not off, it will still run in the background sucking up valuable battery's juice... –  Apr 06 '15 at 09:28
  • 1
    @A.J: ...The method you suggested is not equivalent to the deprecated method `setMobileDataEnabled()`. Perhaps, you should have read, compile, run and trace the Android framework source codes. Then, you would understand what I'm referring to. But judging by your comments and "shouting" in your answer, you have already made up your mind. –  Apr 06 '15 at 09:31
  • @CKD Publishing, I had no intentions of abusing anybody. I wrote what I believed to be true based on good reasons. Indeed my answer is good and thus it has recovered from that -2 to +1 at present. This question is not about turning off the entire network connectivity but the data connectivity only and that is what setMobileDataEnabled did and that is what this solution does. – A.J. Apr 06 '15 at 09:43
  • @A.J.: I see you're new here, so I'll explain how SO voting system works for beginners. You can ask a question and answer your own question but you cannot double-vote on any answers, nor can you upvote your own comments- it's not allowed. Otherwise, SO is opened to nepotism and anarchy as anyone can do anything for their own personal benefit. Thanks for posting your answer here- we need all the opinions we can get. I would recommend, however, not to accuse anyone of doing anything in your answer and comments. – ChuongPham Apr 07 '15 at 01:09
  • From memory- I _do_ read every single answers and comments to my posts that are forwarded to my email address- I don't recall CKD Publishing ever "claims his widget is the only one that works", or wrote his comments based on commercial reasons. Please don't write comments that are untrue and alluded to character assassination. Let's keep this post clean of any personal sentiments and keep it on topic, shall we? Thanks for listening. – ChuongPham Apr 07 '15 at 01:11
  • @ChuongPham, I have very good reasons on which I based my claims. Nevertheless, I am editing my answer just for keeping cool and I hope to put this off topic discussion to an end. But that doesn't change the fact that his comment was vague and had false claims for no other apperent reason. Initially he claimed that it would effect ‘background’ data and now he says the question is for switching mobile network and not data! It would be great if you could give your views on the answer itself. I recall asking you for your opinion via comment and I would highly appreciate your on-topic response. – A.J. Apr 07 '15 at 06:47
  • 2
    @A.J.: When you ran `svc data` command, it references an internal Android class called `DataCommand` which extends the `Svc.Command` class. So, running `svc data` with parameter is equivalent to executing system API call to `com.android.internal.telephony.ITelephony#enableDataConnectivity` and `com.android.internal.telephony.ITelephony#disableDataConnectivity` respectively. Now, according to the comments left in the source codes- there is not a lot- `enableDataConnectivity` and `disableDataConnectivity` enable or disable data connectivity of the mobile network- hence your answer and comment. – ChuongPham Apr 07 '15 at 22:38
  • 2
    The difference between your solution and one proposed by Muzikant is that Muzikant's `setDataEnabled` method does more than just turning data connectivity on/off. It also turned on/off subscription service via the `ServiceManager` class. Subscription service typically handles network traffic for Data, SMS and Voice. So, I guess if it's not switched off, it could use valuable battery power. There are a few more things the `setDataEnabled` method also do, but it's beyond the scope of this post. Lastly, the `setDataEnabled` method replaced the `setMobileDataEnabled()` method from API 21. – ChuongPham Apr 07 '15 at 23:01
  • @ChuongPham, The end result is the same as old setMobileDataEnabled. I guess when data connection is disabled and disconnected, we still want to receive calls and sms, and we still did when using setMobileDataEnabled. There is no battery drain as data connectivity is off and cannot be used by any service unless it turns it on. Does the service shell command does anything more than svc data? I guess not. And the code used after service call phone varies by device. The new setDataEnabled cannot be used by non system apps and that's why this discussion started. – A.J. Apr 08 '15 at 05:13
  • 2
    @A.J.: _"Does the service shell command does anything more than svc data?"_ Yes. The `service call phone` command, when used with transaction code **83**, is equivalent to Muzikant's `setDataEnabled` method. You can confirm this yourself by debugging Android's framework source codes. – ChuongPham Apr 10 '15 at 02:37
  • @A.J.: Also, you mentioned the `service call phone` shell command does not work for you on some mobile devices. Can you please elaborate? I'm interested to know why it did not work since the transaction codes are defined in an internal class, `com.android.internal.telephony.ITelephony`- and this class is not supposed to be changed by any manufacturers. Note: The transaction codes can change between Android versions; but not in the same version by any manufacturers. – ChuongPham Apr 10 '15 at 02:37
  • 2
    @A.J.: Please note that transaction code **83** - `com.android.internal.telephony.ITelephony#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. – ChuongPham Apr 10 '15 at 03:01
  • 1
    @ChoungPham, I referred the sources and found that setDataEnabled(true/false) essentially does the same thing as enableDataConnectivity() and disableDataConnectivity() respectively. There will be no battery drain. And yes the transaction code can vary by device. Manufactures can add their own codes and hence setDataEnabled can be moved to some other code. Your friend CKD knows this as he did not hardcode 83 in his widget. I know exactly what he did. But instead of sharing the info, he decided to advertise his widget (see his comment on your answer). – A.J. Apr 10 '15 at 15:39
3

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;
    }
}
  • "Instead of using 83, I used reflection to get the value of the variable" Can you share this code? Thanks in advance. – nickkadrov Apr 15 '15 at 19:36
  • @nickkadrov: Sorry for my late response, but I've been busy deploying systems for my client. Anyway, I guess you have figured out how to get the value of the TRANSACTION_setDataEnabled field via reflection. My reflection method is not much different from yours- except I'll throw an exception and handle it at the Activity level by informing my users that there is no method available to toggle mobile network on the current Android 5+ rooted device. –  Apr 19 '15 at 02:30
  • yes, I already realised how to do it. Had to dig in Android Sources to find the classes structure. And thank you again for your conclusions post. It really helped me to chose most relaible solution. – nickkadrov Apr 20 '15 at 08:35
3

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.

nickkadrov
  • 488
  • 2
  • 12
2

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.

Community
  • 1
  • 1
rgruet
  • 21
  • 3
2

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;
        }
    }
}
varotariya vajsi
  • 3,965
  • 37
  • 39
  • 1
    Thanks man, that really helped me a lot. Also works on my rooted Android 9 device. – toom Oct 12 '19 at 22:59
2

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
1

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.

Sahil Lombar
  • 145
  • 1
  • 11
1

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:

  1. Download jadx from https://github.com/skylot/jadx
  2. ADB pull the devices framework.jar (adb pull /system/framework/framework.jar)
  3. 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
  4. Find TRANSACTION_enableDataConnectivity and TRANSACTION_disableDataConnectivity, for me these are 38 and 39 respectively
  5. 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.
user196994
  • 13
  • 2