该setMobileDataEnabled方法不再调用由于Android升和后来该setMobile

2019-05-10 09:38发布

我已经登录问题78084与谷歌关于setMobileDataEnabled()方法是通过反射不再调用。 这是可调用由于Android 2.1(API 7)到Android 4.4通过反射(API 19),但由于Android L和以后,即使有根,所述setMobileDataEnabled()方法是不可调用。

官方的回应是,问题是“关闭”,且状态设置为“WorkingAsIntended”。 谷歌的简单解释是:

因为它们不是稳定,恕不另行通知可能会消失私人API是私人。

是的,谷歌,我们都知道使用反射来调用隐藏方法-即使之前的Android来了就scene-的风险,但你需要提供更坚实的答案,备选方案,如果有的话,为了实现相同的结果setMobileDataEnabled() 。 (如果您正在使用谷歌的决定感到不快,因为我,然后登录到发行78084和星它尽可能多地让谷歌知道自己的方式错误。)

所以,我对你的问题是:我们是否在一个死胡同,当涉及到编程方式启用或Android设备上禁用移动网络功能? 从谷歌这样严厉的方式不知何故不跟我坐好。 如果你有解决方法的是Android 5.0(棒棒堂)及以后,我很想听到这个线程你的答案/讨论。

我用下面的代码,看是否setMobileDataEnabled()方法是可用的:

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());
    }
}

但事实并非如此。

更新 :目前,有可能如果设备是植根切换的移动网络。 然而,对于非根深蒂固的设备,它仍然是一个调查的过程,有切换的移动网络没有通用的方法。

Answer 1:

为了延长Muzikant的解决方案#2,有人可以请尝试以下的安卓5.0扎根设备上(如在我目前所不具备的),让我知道,如果它工作或不工作。

要启用或禁用移动数据,请尝试:

// 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

注意: mobile_data变量可以在Android的API 21的源代码被发现在/android-sdk/sources/android-21/android/provider/Settings.java和被声明为:

/**
 * Whether mobile data connections are allowed by the user.  See
 * ConnectivityManager for more info.
 * @hide
*/
public static final String MOBILE_DATA = "mobile_data";

虽然android.intent.action.ANY_DATA_STATE意图可以在Android的API 21的源代码被发现在/android-sdk/sources/android-21/com/android/internal/telephony/TelephonyIntents.java和被声明为:

/**
 * 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";

更新1:如果你不想要实现你的Android应用程序上面的Java代码,那么你就可以运行su通过shell(Linux)或命令提示符(Windows)中如下命令:

adb shell "su -c 'settings put global mobile_data 1; am broadcast -a android.intent.action.ANY_DATA_STATE --ez state 1'"

注: adb位于/android-sdk/platform-tools/目录。 settings命令只支持在Android 4.2或更高版本。 较旧的Android版本将报告"sh: settings: not found"的错误。

更新2:另一种方式来切换移动网络中的的Android 5+设备上是使用未记录的service外壳命令。 下面的命令可经由ADB被执行以切换移动网络:

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

要不就:

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

注1:在使用的交易代码83 service call phone命令的Android版本之间可能会发生变化。 请检查com.android.internal.telephony.ITelephony的价值TRANSACTION_setDataEnabled领域您的Android版本。 此外,而不是硬编码83,你会过使用反射来得到的价值更好TRANSACTION_setDataEnabled场。 通过这种方式,将整个运行Android 5+所有移动品牌工作(如果你不知道如何使用反射来得到的值TRANSACTION_setDataEnabled场,看到从PhongLe解决方案如下─把我从这里复制它。) 重要事项 :请注意,交易代码TRANSACTION_setDataEnabled仅在Android 5.0及更高版本被引入。 在早期版本的Android运行此交易代码没有任何用处,交易代码TRANSACTION_setDataEnabled不存在。

注2: adb位于/android-sdk/platform-tools/目录。 如果你不希望使用ADB,通过执行该方法su在你的应用程序。

注3:请参见下面更新3。

更新3:许多Android开发已经给我发电子邮件关于为Android 5+开/关移动网络的问题,但不是回答个人电子邮件,我会在这里发布我的回答让每个人都可以使用它,适应它自己的Android应用。

第一件事,第一,让我们澄清一些误解和误解有关:

svc data enable
svc data disable

上述方法将仅打开/关闭,而不是订阅服务的背景数据,因此电池会流失公平位由于订阅服务-的Android系统服务-将仍然在后台运行。 对于支持多个SIM卡的Android设备,这种情况更糟糕的是作为订阅服务不断扫描可用的移动网络(S)与在Android设备提供有效的SIM卡使用。 需要您自担风险使用此方法。

现在,有道关掉移动网络,包括通过它的相应的订阅服务SubscriptionManager在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 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;
    }           
}

要检查是否在移动网络启用与否:

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;
}

要获取的值TRANSACTION_setDataEnabled场(从下面PhongLe的解决方案借来的):

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;
    }
}

要通过执行命令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;
        }
    }
}

希望本次更新清除了任何误解,误解或质疑你可能有关于Android的根深蒂固5+设备的开/关切换的移动网络。



Answer 2:

我注意到,张贴ChuongPham服务调用方法不会在所有设备上一致地工作。

我发现下面的解决方案,我认为,将没有任何问题的工作在所有已解锁装置。

执行经以下

为了使移动数据

svc data enable

要禁用移动数据

svc data disable

我觉得这是最简单也是最好的办法。

编辑:2个downvotes是什么,我认为是商业原因。 此人现在已经删除了他的意见。 自己尝试一下,它的作品! 也证实了家伙在评论工作。



Answer 3:

只是分享一些更多的见解和可能的解决方案(对于植根设备和系统的应用程序)。

解决方案#1

这似乎是setMobileDataEnabled方法不再存在ConnectivityManager而这一功能被转移到TelephonyManager两种方法getDataEnabledsetDataEnabled 。 我试图调用这些方法与反思,你可以在下面的代码中看到:

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;
}

当执行代码,你会得到一个SecurityException指出, Neither user 10089 nor current process has android.permission.MODIFY_PHONE_STATE.

所以,是的,这是内部API的预期变化,不再到使用以前版本的破解应用程序。

(开始咆哮:那个可怕的android.permission.MODIFY_PHONE_STATE许可......最终咆哮)。

好消息是如果你正在构建能够获得MODIFY_PHONE_STATE权限(仅在系统的应用程序可以使用)的应用程序,你可以使用上面的代码来切换移动数据状态。

解决方案#2

要检查移动数据可以使用的当前状态mobile_data领域Settings.Global (不是官方文档中介绍)。

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

并启用/禁用移动数据你可以扎根设备上使用shell命令(只是基本的测试执行,从而在评论任何反馈表示赞赏)。 可以运行下面的命令(或多个)作为根(1 =允许,0 =禁止):

settings put global mobile_data 1
settings put global mobile_data 0


Answer 4:

我没有足够的信誉发表评论,但我已经尝试了所有的答案,发现如下:

ChuongPham:不要使用83,我用反射来获取变量的值TRANSACTION_setDataEnabledcom.android.internal.telephony.ITelephony因此它可以支持所有Android设备5+,无论品牌。

Muzikant:如果工作中的应用程序移动到/system/priv-app/ (感谢rgruet)目录否则,它通过根的作品,太! 你只需要通知用户应用程序将需要重新启动之前更改到移动网络将发生。

AJ:与工作排序。 所以我测试的设备耗尽他们的电池公平一点不会关闭订阅服务。 AJ的解决方案并不等同于,尽管要求Muzikant的解决方案。 我可以通过调试不同的三星,索尼,LG和股票ROM的(我彻底)证实了这一点,可以反驳AJ的要求,他的解决方案是一样的Muzikant的。 (注:我不能让我的手在一些Nexus和摩托罗拉ROM的所以没有测试这些ROM与提出的解决方案。)

不管怎么说,希望这清除了在解决方案的任何疑问。

编码愉快! PL,德国

更新 :对于那些想知道如何得到的值TRANSACTION_setDataEnabled通过反射场,你可以做到以下几点:

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;
    }
}


Answer 5:

我发现, su -c 'service call phone 83 i32 1'的解决方案是最可靠的解锁装置。 由于勒的Phong参考我已经通过获得使用反射厂商/ OS交易特定代码改进它。 也许这将是为别人有用。 所以,这里是源代码:

    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 "";
    }

更新:

我的一些用户报告说,他们有问题,通过这种方法接通移动网络(关闭工作正确的)。 有没有人有解决方案吗?

UPDATE2:

经过一番挖掘了Android 5.1代码中,我发现,他们改变了交易的签名。 搭载Android 5.1带来的多SIM的官方支持。 因此,本次交易不需要所谓的订阅ID作为第一个参数( 在这里阅读更多 )。 这种情况的结果是,命令su -c 'service call phone 83 i32 1'无法在移动网络开启Android 5.1。 因此,在Android 5.1的完整的命令应该是这样的su -c 'service call phone 83 i32 0 i32 1' (在i32 0是SUBID,在i32 1是命令0 -关闭,1 -上)。 我已经更新上面的此修复程序的代码。



Answer 6:

解决方案#1从Muzikant似乎工作,如果你的.apk文件移动到使应用程序“系统” /system/priv-app/文件夹,而不是/system/app/一个(@jaumard:也许这就是为什么你的测试没有工作)。

当.apk文件在/system/priv-app/文件夹,就可以成功申请可怕android.permission.MODIFY_PHONE_STATE许可的清单,并呼吁TelephonyManager.setDataEnabledTelephonyManager.getDataEnabled

当时在Nexus 5 /的是Android 5.0的工作最少。 .apk文件烫发是0144 。 您需要重新启动的变化对器件进行考虑,也许这是可以避免的-看到这个线程 。



Answer 7:

要纠正Muzikant解决方案#2

settings put global mobile_data 1

是否能够让只对移动数据的切换,但没有采取任何措施的连通性。 只有切换启用。 为了让使用数据的工作

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

给出错误的额外费用

android.intent.action.ANY_DATA_STATE

需要String对象,而--ez参数用于布尔。 参考文献:PhoneGlobals.java&PhoneConstants.java。 使用后的连接或连接为额外使用命令

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

仍然没有做任何事情,以使数据。



Answer 8:

我衍生自@ChuongPham和@AJ最终代码启用和禁用蜂窝数据。 为使您可以拨打setMobileDataEnabled(真); 和禁用可以调用setMobileDataEnabled(假);

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;
        }
    }
}


文章来源: The setMobileDataEnabled method is no longer callable as of Android L and later