Метод setMobileDataEnabled больше не может быть вызван как на Android L, так и позже

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

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

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

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

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

Я использовал код ниже, чтобы узнать, setMobileDataEnabled() ли метод 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 : В настоящее время можно переключать мобильную сеть, если устройство коренится. Однако для не-корневых устройств он все еще является следственным процессом, поскольку универсальный метод переключения мобильной сети не существует.

Solutions Collecting From Web of "Метод setMobileDataEnabled больше не может быть вызван как на Android L, так и позже"

Чтобы расширить решение 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 может быть найдена в исходных кодах API 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"; 

ОБНОВЛЕНИЕ 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" .

ОБНОВЛЕНИЕ 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. Кроме того, вместо hardcoding 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 в вашем приложении.

Примечание 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 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; } } 

Чтобы проверить, включена ли мобильная сеть или нет:

 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+.

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

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

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

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

 svc data enable 

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

 svc data disable 

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

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

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

Решение №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 разрешение … end rant).

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

Решение №2

Чтобы проверить текущее состояние мобильных данных, вы можете использовать поле mobile_data в Settings.Global (не задокументировано в официальной документации).

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

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

 settings put global mobile_data 1 settings put global mobile_data 0 

Я обнаружил, что решение su -c 'service call phone 83 i32 1' является самым надежным для укоренившихся устройств. Благодаря ссылке Phong Le, я улучшил ее, получив код транзакции поставщика / 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-карты. Таким образом, транзакции требуется так называемый идентификатор подписки в качестве первого параметра ( подробнее здесь ). Результатом этой ситуации является то, что команда 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 – off и 1 – on). Я обновил код выше с помощью этого исправления.

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

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

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

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

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

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

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

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

Счастливое кодирование! 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; } } 

Чтобы исправить решение 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 

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