在 android 5.0+ 上尝试通过 appium setting 设置移动网络状态时无效:
# python
from appium.webdriver.connectiontype import ConnectionType
self.driver.set_network_connection(ConnectionType.DATA_ONLY)
设置后不报错,但移动网络并没有启动。尝试过其他模式均没问题。
先看源码:
... private boolean setDataConnection(boolean on) { try { if (Build.VERSION.SDK_INT == Build.VERSION_CODES.FROYO) { Method dataConnSwitchmethod; Class<?> telephonyManagerClass; Object ITelephonyStub; Class<?> ITelephonyClass;
TelephonyManager telephonyManager = (TelephonyManager) mContext
.getSystemService(Context.TELEPHONY_SERVICE);
telephonyManagerClass = Class.forName(telephonyManager.getClass().getName());
Method getITelephonyMethod = telephonyManagerClass.getDeclaredMethod("getITelephony");
getITelephonyMethod.setAccessible(true);
ITelephonyStub = getITelephonyMethod.invoke(telephonyManager);
ITelephonyClass = Class.forName(ITelephonyStub.getClass().getName());
if (on) {
dataConnSwitchmethod = ITelephonyClass
.getDeclaredMethod("enableDataConnectivity");
} else {
dataConnSwitchmethod = ITelephonyClass
.getDeclaredMethod("disableDataConnectivity");
}
dataConnSwitchmethod.setAccessible(true);
dataConnSwitchmethod.invoke(ITelephonyStub);
} else {
//log.i("App running on Ginger bread+");
final ConnectivityManager conman = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
final Class<?> conmanClass = Class.forName(conman.getClass().getName());
final Field iConnectivityManagerField = conmanClass.getDeclaredField("mService");
iConnectivityManagerField.setAccessible(true);
final Object iConnectivityManager = iConnectivityManagerField.get(conman);
final Class<?> iConnectivityManagerClass = Class.forName(iConnectivityManager.getClass().getName());
final Method setMobileDataEnabledMethod = iConnectivityManagerClass.getDeclaredMethod("setMobileDataEnabled", Boolean.TYPE);
setMobileDataEnabledMethod.setAccessible(true);
setMobileDataEnabledMethod.invoke(iConnectivityManager, on);
}
return true;
} catch(Exception e) {
Log.e(TAG,"error turning on/off data: " + e.getMessage());
return false;
}
}
...
大致流程:
1. 判断是否是 android 2.2。如果是,使用 TelephonyManager 的 enableDataConnectivity 或 disableDataConnectivity 控制移动网络
2. 如果不是 2.2(即 2.3+),使用 ConnectivityManager 的 setMobileDataEnabled 控制移动网络
上面两个都是使用反射机制调用 android 的隐藏 api 实现的。
Google 一番,找到如下解答:
[Android L (5.x) Turn ON/OFF “Mobile Data” programmatically](http://stackoverflow.com/questions/29340150/android-l-5-x-turn-on-off-mobile-data-programmatically):
> In Android L 5.xx the hidden API setMobileDataEnabled method is
> removed and it can no longer be used. You can verify this in android
> lolipop source code under
> /frameworks/base/core/java/android/net/ConnectivityManager.java.
>
> If you still insist to perform it, you can use code snippet answered
> by Kushal but getDataEnabled is a system api, which normal user
> applications cant access. There is also one more system api available
> setDataEnabled under TelephonyManager.
> (/frameworks/base/telephony/java/android/telephony/TelephonyManager.java)
即代码中在 android 2.3+ 上使用的方法已经无效(隐藏 api 已经被去掉)
# 解决方法
Stackoverflow 上找到两个看起来比较靠谱的方法(条件所限,目前均未试验过):
1、 调用另一个新的隐藏 API :`setDataEnabled` 。
> 参考<http://stackoverflow.com/questions/29340150/android-l-5-x-turn-on-off-mobile-data-programmatically>
```java
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;
}
缺点: 需要 android.permission.MODIFY_PHONE_STATE 权限(这是系统级权限,普通 app 获取不到,系统 app 才能拿到)
2、调用 shell 命令
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 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;
}
}
}
缺点:需要 root 权限
# 总结
1. 学到了一种调用系统隐藏 API 的方法(反射)
2. 了解了如何能够设置数据连接的开关
报了一个 issue :<https://github.com/appium/io.appium.settings/issues/5>