it-swarm.com.ru

Метод setMobileDataEnabled больше не вызывается в Android L и более поздних версиях.

Я зарегистрировался в выпуск 78084 в Google относительно того, что метод setMobileDataEnabled() больше не вызывается с помощью отражения. Он вызывался с Android 2.1 (API 7) до Android 4.4 (API 19) через отражение, но начиная с Android L и более поздних версий, даже с root, метод setMobileDataEnabled() не вызывается.

Официальный ответ заключается в том, что проблема «Закрыто» и статус установлен на «WorkingAsIntended». Простое объяснение Google:

Частные API являются частными, потому что они нестабильны и могут исчезнуть без уведомления.

Да, Google, мы знаем о риске использования отражения для вызова скрытого метода - даже до того, как Android появился на сцене, - но вам нужно предоставить более четкий ответ относительно альтернатив, если таковые имеются, для достижения того же результата, что и setMobileDataEnabled(). (Если вы недовольны решением Google, как я, войдите в Issue 78084 и отметьте его как можно больше, чтобы Google узнал об ошибке своего пути.)

Итак, мой вопрос к вам: находимся ли мы в тупике, когда речь идет о программном включении или отключении функции мобильной сети на устройстве Android? Этот упрямый подход от Google как-то не устраивает меня. Если у вас есть обходной путь для Android 5.0 (Lollipop) и выше, я хотел бы услышать ваш ответ/обсуждение в этой теме.

Я использовал приведенный ниже код, чтобы проверить, доступен ли метод 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());
    }
}

Но это не так.

UPDATE: В настоящее время можно переключать мобильную сеть, если устройство имеет root-права. Однако для некорневых устройств это все еще процесс расследования, поскольку не существует универсального метода переключения мобильной сети.

46
ChuongPham

Чтобы расширить Решение Muzikant №2, кто-нибудь может, пожалуйста, попробовать приведенное ниже решение на устройстве с ОС Android 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 Intent можно найти в исходных кодах 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";

UPDATE 1: если вы не хотите реализовывать вышеупомянутые коды Java в своем приложении Android, вы можете запустить команды su через командную консоль (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".

UPDATE 2: еще один способ переключения мобильной сети на устройстве Android 5+ с rooted - использовать недокументированную команду оболочки 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 вам лучше использовать Reflection, чтобы получить значение поля TRANSACTION_setDataEnabled. Таким образом, он будет работать на всех мобильных брендах под управлением Android 5+ (если вы не знаете, как использовать Reflection, чтобы получить значение поля TRANSACTION_setDataEnabled, см. Решение из PhongLe ниже - избавьте меня от дублирования здесь.) Важно: обратите внимание, что код транзакции TRANSACTION_setDataEnabled был введен только в Android 5.0 и более поздних версиях. Запуск этого кода транзакции в более ранних версиях Android ничего не даст, поскольку код транзакции TRANSACTION_setDataEnabled не существует.

Примечание 2: adb находится в каталоге /Android-sdk/platform-tools/. Если вы не хотите использовать ADB, выполните метод через su в своем приложении. </ S>

Примечание 3: см. ОБНОВЛЕНИЕ 3 ниже.

ОБНОВЛЕНИЕ 3: Многие разработчики Android отправили мне по электронной почте вопросы о включении/выключении мобильной сети для Android 5+, но вместо того, чтобы отвечать на отдельные электронные письма, я опубликую свой ответ здесь, чтобы каждый мог его использовать и адаптировать под свои требования. Приложения для Android.

Прежде всего, давайте проясним некоторые заблуждения и недоразумения относительно:

svc data enable
svc data disable

Вышеуказанные методы будут включать/выключать только фоновые данные, не службу подписки, поэтому батарея разряжается, так как служба подписки - системная служба Android - все еще будет работать в фоновом режиме. Для устройств Android, поддерживающих несколько сим-карт, этот сценарий хуже, поскольку служба подписки постоянно сканирует доступные мобильные сети для использования с активными SIM-картами, доступными на устройстве Android. Используйте этот метод на свой страх и риск.

Теперь правильный способ отключения мобильной сети, включая соответствующую услугу подписки через класс 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+ с root-доступом.

25
ChuongPham

Я заметил, что метод служебного вызова, опубликованный ChuongPham, не работает согласованно на всех устройствах.

Я нашел следующее решение, которое, я думаю, будет работать без проблем на всех устройствах с ROOTED. 

Выполните следующее через su

Чтобы включить мобильные данные

svc data enable

Чтобы отключить мобильные данные

svc data disable

Я думаю, что это самый простой и лучший метод.

Правка: 2 downvotes были по коммерческим причинам, которые я считаю. Пользователь удалил свой комментарий сейчас. Попробуйте сами, все работает! Также подтвердили работу ребята в комментариях.

9
A.J.

Просто для того, чтобы поделиться несколькими идеями и возможными решениями (для корневых устройств и системных приложений).

Решение № 1

Похоже, что метод setMobileDataEnabled больше не существует в ConnectivityManager, и эта функциональность была перемещена в TelephonyManager с двумя методами getDataEnabled и setDataEnabled. Я пытался вызывать эти методы с отражением, как вы можете видеть в коде ниже:

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_dataSettings.Global (не задокументировано в официальной документации).

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

А чтобы включить/отключить мобильные данные, вы можете использовать команды Shell на корневых устройствах (выполняется только базовое тестирование, поэтому любая обратная связь в комментариях приветствуется) . Вы можете выполнить следующие команды в качестве пользователя root (1 = включить, 0 = отключить):

settings put global mobile_data 1
settings put global mobile_data 0
6
Muzikant

Я обнаружил, что решение su -c 'service call phone 83 i32 1' наиболее надежно для рутованных устройств. Благодаря справке Phong Le я улучшил ее, получив специальный код транзакции для поставщика/ОС с помощью отражения. Может быть, это будет полезно для кого-то еще. Итак, вот исходный код:

    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. Таким образом, для транзакции в качестве первого параметра необходим так называемый Subscription Id ( подробнее здесь ). Результатом этой ситуации является то, что команда su -c 'service call phone 83 i32 1' не включает Mobile Net на Android 5.1. Таким образом, полная команда на Android 5.1 должна выглядеть следующим образом: su -c 'service call phone 83 i32 0 i32 1' (i32 0 - это subId, i32 1 - это команда 0 - выкл. И 1 - вкл.). Я обновил код выше с помощью этого исправления.

3
nickkadrov

У меня недостаточно репутации, чтобы комментировать, но я перепробовал все ответы и нашел следующее:

ChuongPham: Вместо того, чтобы использовать 83 , я использовал отражение, чтобы получить значение переменной TRANSACTION_setDataEnabled из com.Android.internal.telephony.ITelephony, чтобы она работала на всех устройствах Android 5+, независимо от брендов.

Muzikant: Работать, если приложение перемещено в каталог /system/priv-app/ (благодаря rgruet.) Иначе, оно работает и через root! Вам просто нужно сообщить своим пользователям, что приложение должно быть перезагружено до того, как произойдут изменения в мобильной сети.

AJ: Работа - вроде. Служба подписки не отключается, поэтому устройства, которые я тестировал, разрядили свои батареи. Решение AJНЕэквивалентно решению Muzikant, несмотря на претензию. Я могу подтвердить это, отладив различные стандартные ПЗУ Samsung, Sony и LG (я в курсе), и могу опровергнуть утверждение AJ о том, что его решение совпадает с решением Muzikant. (Примечание: я не могу достать некоторые ПЗУ Nexus и Motorola, поэтому не тестировал эти ПЗУ с предлагаемыми решениями.)

Во всяком случае, надеюсь, что это устранит любые сомнения по поводу решений.

Удачного кодирования! PL, Германия

UPDATE: Для тех, кто интересуется, как получить значение поля 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;
    }
}
3
user4750643

Решение №1 от Muzikant, похоже, сработает, если вы сделаете приложение «системным», переместив .apk в папку /system/priv-app/, not в папку /system/app/ (@jaumard: возможно, именно поэтому ваш тест не сработал).

Когда файл .apk находится в папке /system/priv-app/, он может успешно запросить ужасное разрешение Android.permission.MODIFY_PHONE_STATE в манифесте и вызвать TelephonyManager.setDataEnabled и TelephonyManager.getDataEnabled

По крайней мере, это работает на Nexus 5/Android 5.0. Перечень .apk - 0144. Вам необходимо перезагрузить устройство, чтобы изменения были приняты во внимание, возможно, этого можно избежать - см. эту ветку .

2
rgruet

Исправить Muzikant Solution # 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

Все еще не делает ничего, чтобы включить данные. 

1
Sahil Lombar

Я получил окончательный код от @ChuongPham и @ A.J. для включения и отключения сотовых данных. для включения вы можете вызвать setMobileDataEnabled (true); а для отключения вы можете вызвать 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;
        }
    }
}
0
varotariya vajsi