Метод 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 появился на сцене, - но вам нужно предоставить более четкий ответ относительно альтернатив, если таковые имеются, для достижения того же результата, что и для Android. setMobileDataEnabled()
, (Если вы недовольны решением Google как я, войдите в выпуск 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());
}
}
Но это не так.
ОБНОВЛЕНИЕ: В настоящее время возможно переключить мобильную сеть, если устройство рутировано. Однако для некорневых устройств это все еще процесс расследования, поскольку не существует универсального метода переключения мобильной сети.
8 ответов
Чтобы расширить Решение 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
Намерение можно найти в исходных кодах 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. Кроме того, вместо жесткого кодирования 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 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-доступом.
Просто для того, чтобы поделиться несколькими идеями и возможными решениями (для корневых устройств и системных приложений).
Решение № 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_data
поле Settings.Global
(не задокументировано в официальной документации).
Settings.Global.getInt(contentResolver, "mobile_data");
А чтобы включить / отключить мобильные данные, вы можете использовать команды оболочки на корневых устройствах (выполняется только базовое тестирование, поэтому любые отзывы в комментариях приветствуются). Вы можете запустить следующие команды от имени пользователя root (1= включить, 0= отключить):
settings put global mobile_data 1
settings put global mobile_data 0
Я заметил, что метод служебного вызова, опубликованный ChuongPham, не работает согласованно на всех устройствах.
Я нашел следующее решение, которое, я думаю, будет работать без проблем на всех устройствах с ROOTED.
Выполните следующее через su
Чтобы включить мобильные данные
svc data enable
Чтобы отключить мобильные данные
svc data disable
Я думаю, что это самый простой и лучший метод.
Изменить: 2 понижения были по тем причинам, которые я считаю коммерческими. Пользователь удалил свой комментарий сейчас. Попробуйте сами, это работает! Также подтвердили работу ребята в комментариях.
Я нашел это 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 - включена). Я обновил код выше с помощью этого исправления.
У меня недостаточно репутации, чтобы комментировать, но я перепробовал все ответы и нашел следующее:
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, Германия
ОБНОВЛЕНИЕ: Для тех, кто интересуется, как получить значение 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;
}
}
Решение №1 от Muzikant, похоже, сработает, если вы сделаете приложение "системным", переместив.apk в /system/priv-app/
папка, а не в /system/app/
один (@jaumard: возможно, именно поэтому ваш тест не сработал).
Когда.apk находится в /system/priv-app/
папка, он может успешно запросить ужасный android.permission.MODIFY_PHONE_STATE
разрешение в манифесте и вызов TelephonyManager.setDataEnabled
а также TelephonyManager.getDataEnabled
,
По крайней мере, это работает на Nexus 5/ Android 5.0. Пермь.apk 0144
, Вам нужно перезагрузить устройство, чтобы изменения были приняты во внимание, возможно, этого можно избежать - см. Эту ветку.
Не все телефоны и версии Android имеют одинаковые функции включения / отключения мобильных данных. в противном случае это решение протестировано на моем телефоне (SAMSUNG SM-J100H)
Чтобы включить мобильные данные:
adb shell service call phone 27
Чтобы отключить мобильные данные:
adb shell service call phone 28
Я получил окончательный код от @ChuongPham и @AJ для включения и отключения сотовых данных. для включения вы можете вызвать 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;
}
}
}
Следующее решение работает путем включения и отключения мобильных данных (как при нажатии на ползунок "Мобильные данные"). Требуется рут. Протестировано на LineageOS 16.0 (с root-доступом):
Шаги 1-3 взяты из принятого ответа при отключении второй SIM-карты через adb shell / Tasker - с использованием действий / намерений:
- Загрузите jadx с https://github.com/skylot/jadx
- ADB извлекает файлы framework.jar (adb pull /system/framework/framework.jar)
- Откройте файл.jar с помощью 7-Zip и извлеките файлы *.dex. Откройте каждый файл.dex с помощью jadx-gui, пока не найдете файл со следующим деревом: com.android.internal.telephony.ITelephony
- Найдите TRANSACTION_enableDataConnectivity и TRANSACTION_disableDataConnectivity, для меня это 38 и 39 соответственно
- Из корневой оболочки (например, adb shell или Termux) запустите
service call phone 38
для включения данных иservice call phone 39
чтобы отключить данные.
Исправить 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
Все еще не делает ничего, чтобы включить данные.