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

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

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