studio - El método setMobileDataEnabled ya no se puede llamar a partir de Android L y posterior
on save instance state android (8)
He registrado el problema 78084 con Google sobre el método setMobileDataEnabled()
que ya no se puede llamar a través de la reflexión. Fue invocable desde Android 2.1 (API 7) a Android 4.4 (API 19) mediante reflexión, pero a partir de Android L y posterior, incluso con root, el método setMobileDataEnabled()
no se puede llamar.
La respuesta oficial es que el problema es "Cerrado" y el estado establecido en "Trabajar como está". La explicación simple de Google es:
Las API privadas son privadas porque no son estables y pueden desaparecer sin previo aviso.
Sí, Google, somos conscientes del riesgo de utilizar la reflexión para llamar al método oculto, incluso antes de que Android aparezca en escena, pero debe proporcionar una respuesta más sólida en cuanto a las alternativas, si las hay, para lograr el mismo resultado que setMobileDataEnabled()
(Si no está satisfecho con la decisión de Google tal como soy, inicie sesión en el Issue 78084 y marque la mayor cantidad posible para que Google sepa el error de su camino).
Entonces, mi pregunta para usted es: ¿estamos en un callejón sin salida cuando se trata de habilitar o deshabilitar programáticamente la función de red móvil en un dispositivo Android? Este enfoque de mano dura de Google de alguna manera no me sienta bien. Si tiene una solución para Android 5.0 (Lollipop) y más allá, me gustaría escuchar su respuesta / discusión en este hilo.
He usado el código a continuación para ver si el método setMobileDataEnabled()
está disponible:
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());
}
}
Pero no lo es.
ACTUALIZACIÓN : actualmente, es posible alternar la red móvil si el dispositivo está rooteado. Sin embargo, para dispositivos no rooteados, todavía es un proceso de investigación ya que no hay un método universal para alternar la red móvil.
Descubrí que su -c ''service call phone 83 i32 1''
solución su -c ''service call phone 83 i32 1''
es la más confiable para dispositivos rooteados. Gracias a la referencia de Phong Le, lo he mejorado obteniendo el código de transacción específico del vendedor / os usando la reflexión. Tal vez sea útil para otra persona. Entonces, aquí está el código fuente:
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 "";
}
Actualizar:
Algunos de mis usuarios informan que tienen problemas para activar la red móvil a través de este método (desactivar los trabajos correctos). ¿Alguien tiene solución?
Actualización2:
Después de investigar el código de Android 5.1, descubrí que cambiaron la firma de la transacción. Android 5.1 ofrece soporte oficial de multi-SIM. Por lo tanto, la transacción necesita el denominado Id. De suscripción como primer parámetro ( SubscriptionManager ). El resultado de esta situación es que el comando su -c ''service call phone 83 i32 1''
no enciende Mobile Net en Android 5.1. Por lo tanto, el comando completo en Android 5.1 debería ser como este su -c ''service call phone 83 i32 0 i32 1''
(el i32 0
es el subId, el i32 1
es el comando 0 - off y 1 - on). He actualizado el código anterior con esta solución.
La solución n. ° 1 de Muzikant parece funcionar si crea el "sistema" de la aplicación moviendo el archivo .apk a la carpeta /system/priv-app/
, no a /system/app/
one (@jaumard: tal vez por eso su prueba no funcionó).
Cuando .apk está en la carpeta /system/priv-app/
, puede solicitar con éxito el espantoso permiso android.permission.MODIFY_PHONE_STATE
en el Manifiesto y llamar a TelephonyManager.setDataEnabled
y TelephonyManager.getDataEnabled
.
Al menos eso funciona en Nexus 5 / Android 5.0. Las perms de .apk son 0144
. Necesita reiniciar el dispositivo para que se tenga en cuenta el cambio, quizás esto podría evitarse - vea este hilo .
No tengo suficiente reputación para comentar, pero probé todas las respuestas y encontré lo siguiente:
ChuongPham: En lugar de usar 83 , utilicé el reflejo para obtener el valor de la variable TRANSACTION_setDataEnabled
de com.android.internal.telephony.ITelephony
por lo que funciona en todos los dispositivos Android 5+, independientemente de las marcas.
Muzikant: Funciona si la aplicación se mueve a /system/priv-app/
directory (gracias a rgruet ). ¡De lo contrario, también funciona a través de root! Solo debe informar a sus usuarios que la aplicación necesitará un reinicio antes de que se realicen los cambios en la red móvil.
AJ: Trabajo, más o menos. No apaga el servicio de suscripción, por lo que los dispositivos que probé agotaron sus baterías un poco. La solución de AJ NO es equivalente a la solución de Muzikant a pesar del reclamo. Puedo confirmar esto depurando diferentes ROM de acciones de Samsung, Sony y LG (estoy completo) y puedo refutar la afirmación de AJ de que su solución es la misma que la de Muzikant. (Nota: no puedo tener en mis manos algunas ROM de Nexus y Motorola, así que no he probado estas ROM con las soluciones propuestas).
De todos modos, espero que aclare cualquier duda sobre las soluciones.
Feliz codificación! PL, Alemania
ACTUALIZACIÓN : para aquellos que se preguntan cómo obtener el valor del campo TRANSACTION_setDataEnabled
través de la reflexión, puede hacer lo siguiente:
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;
}
}
Noté que el método de llamada de servicio publicado por ChuongPham no funciona de manera consistente en todos los dispositivos.
He encontrado la siguiente solución que, creo, funcionará sin problemas en todos los dispositivos ROOTED.
Ejecute lo siguiente a través de su
Para habilitar los datos móviles
svc data enable
Para deshabilitar datos móviles
svc data disable
Creo que este es el método más simple y mejor.
Edición: 2 downvotes fueron por razones comerciales. La persona ha eliminado su comentario ahora. Pruébalo tú mismo, ¡funciona! También confirmado para trabajar por chicos en los comentarios.
Obtuve el código final de @ChuongPham y @AJ para habilitar y deshabilitar datos móviles. para habilitar puede llamar a setMobileDataEnabled (true); y para deshabilitar puede llamar a 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;
}
}
}
Para corregir la solución Muzikant # 2
settings put global mobile_data 1
Solo habilita el alternar para datos móviles pero no hace nada con la conectividad. Solo el alternar está habilitado. Para que los datos funcionen usando
su -c am broadcast -a android.intent.action.ANY_DATA_STATE --ez state 1
Da error como extra para
android.intent.action.ANY_DATA_STATE
Requiere String Object while --ez parámetro se usa para boolean. Ref: PhoneGlobals.java y PhoneConstants.java. Después de usar conectando o conectado como extra usando el comando
su -c am broadcast -a android.intent.action.ANY_DATA_STATE --es state connecting
Todavía no hace nada para habilitar los datos.
Solo para compartir algunas ideas más y una posible solución (para dispositivos rooteados y aplicaciones de sistema).
Solución n. ° 1
Parece que el método setMobileDataEnabled
ya no existe en ConnectivityManager
y esta funcionalidad se movió a TelephonyManager
con dos métodos getDataEnabled
y setDataEnabled
. Intenté llamar a estos métodos con reflexión, como puede ver en el siguiente código:
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;
}
Al ejecutar el código, se obtiene una SecurityException
que establece que Neither user 10089 nor current process has android.permission.MODIFY_PHONE_STATE.
Por lo tanto, sí, este es un cambio intencionado para la API interna y ya no está disponible para las aplicaciones que usaron ese truco en versiones anteriores.
(empieza a despotricar: ese terrible permiso de android.permission.MODIFY_PHONE_STATE ... fin de rant).
La buena noticia es que, en caso de que esté creando una aplicación que pueda adquirir el permiso MODIFY_PHONE_STATE (solo las aplicaciones del sistema pueden usar eso), puede usar el código anterior para alternar el estado de los datos móviles.
Solución n. ° 2
Para verificar el estado actual de los datos móviles, puede usar el campo mobile_data
de Settings.Global
(no documentado en la documentación oficial).
Settings.Global.getInt(contentResolver, "mobile_data");
Y para habilitar / deshabilitar los datos móviles puede usar comandos de shell en dispositivos rooteados (solo se realizan pruebas básicas para que cualquier comentario en los comentarios sea apreciado). Puede ejecutar los siguientes comandos como root (1 = enable, 0 = disable):
settings put global mobile_data 1
settings put global mobile_data 0
Para ampliar la Solución # 2 de Muzikant, ¿alguien puede probar la siguiente solución en un dispositivo rooteado con Android 5.0 (ya que actualmente no tengo ninguno) y decirme si funciona o no funciona?
Para habilitar o deshabilitar datos móviles, intente:
// 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
Nota: La variable mobile_data
se puede encontrar en los códigos fuente de Android API 21 en /android-sdk/sources/android-21/android/provider/Settings.java
y se declara como:
/**
* Whether mobile data connections are allowed by the user. See
* ConnectivityManager for more info.
* @hide
*/
public static final String MOBILE_DATA = "mobile_data";
Mientras que la intención android.intent.action.ANY_DATA_STATE
se puede encontrar en los códigos fuente de Android API 21 en /android-sdk/sources/android-21/com/android/internal/telephony/TelephonyIntents.java
y se declara como:
/**
* 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";
ACTUALIZACIÓN 1 : si no desea implementar los códigos Java anteriores en su aplicación Android, puede ejecutar los comandos su
a través de un shell (Linux) o símbolo del sistema (Windows) de la siguiente manera:
adb shell "su -c ''settings put global mobile_data 1; am broadcast -a android.intent.action.ANY_DATA_STATE --ez state 1''"
Nota: adb
se encuentra en el directorio /android-sdk/platform-tools/
. El comando de settings
solo es compatible con Android 4.2 o posterior. La versión anterior de Android reportará un error "sh: settings: not found"
.
ACTUALIZACIÓN 2 : Otra forma de alternar la red móvil en un dispositivo Android rooteado 5+ sería usar el comando del shell del service
no documentado. El siguiente comando se puede ejecutar a través de ADB para alternar la red móvil:
// 1: Enable; 0: Disable
adb shell "su -c ''service call phone 83 i32 1''"
O solo:
// 1: Enable; 0: Disable
adb shell service call phone 83 i32 1
Nota 1 : El código de transacción 83 utilizado en el comando de service call phone
puede cambiar entre las versiones de Android. Compruebe com.android.internal.telephony.ITelephony
por el valor del campo TRANSACTION_setDataEnabled
para su versión de Android. Además, en lugar de hardcoding 83 , sería mejor usar Reflection para obtener el valor del campo TRANSACTION_setDataEnabled
. De esta forma, funcionará en todas las marcas móviles con Android 5+ (si no sabe cómo usar Reflection para obtener el valor del campo TRANSACTION_setDataEnabled
, consulte la solución de PhongLe a continuación, sálvame de duplicarlo aquí). Importante : Tenga en cuenta que el código de transacción TRANSACTION_setDataEnabled
solo se ha introducido en Android 5.0 y versiones posteriores. Ejecutar este código de transacción en versiones anteriores de Android no hará nada ya que el código de transacción TRANSACTION_setDataEnabled
no existe.
Nota 2 : adb
está ubicado en el directorio /android-sdk/platform-tools/
. Si no desea utilizar ADB, ejecute el método a través de su
en su aplicación.
Nota 3 : Vea la ACTUALIZACIÓN 3 a continuación.
ACTUALIZACIÓN 3 : Muchos desarrolladores de Android me han enviado preguntas sobre cómo activar / desactivar la red móvil para Android 5+, pero en lugar de responder correos electrónicos individuales, publicaré mi respuesta aquí para que todos puedan usarla y adaptarla a sus aplicaciones de Android.
Lo primero es aclarar algunos malentendidos y malentendidos con respecto a:
svc data enable
svc data disable
Los métodos anteriores solo activarían / desactivarían los datos en segundo plano, no el servicio de suscripción, por lo que la batería se agotaría bastante, ya que el servicio de suscripción, un servicio del sistema Android, seguirá ejecutándose en segundo plano. Para los dispositivos Android compatibles con varias tarjetas SIM, este escenario es peor ya que el servicio de suscripción escanea constantemente las redes móviles disponibles para usar con las tarjetas SIM activas disponibles en el dispositivo Android. Use este método bajo su propio riesgo.
Ahora, la forma correcta de desactivar la red móvil, incluido su servicio de suscripción correspondiente a través de la clase SubscriptionManager
presentada en API 22, es:
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;
}
}
Para verificar si la red móvil está habilitada o no:
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;
}
Para obtener el valor del campo TRANSACTION_setDataEnabled
(tomado de la solución de PhongLe a continuación):
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;
}
}
Para ejecutar el comando a través de 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;
}
}
}
Espero que esta actualización aclare cualquier malentendido, malentendido o pregunta que pueda tener acerca de cómo activar / desactivar la red móvil en dispositivos Android 5+ rooteados.