1、现象:
在测试过程中,发现了一个bug。场景是:在Android6.0的机器上,连接一个系统保存过的wifi,输入了正确的密码后,却始终无法连接成功,即updateNetwork始终返回-1.
2、分析:
首先简要说一下wifi的连接过程。我们使用系统api对当前要连接的wifi进行判断:(1)如果系统未保存过,则创建一个WifiConfiguration,调用WifiManager的addNetwork方法去创建新的wifi连接;(2)如果是系统保存过的,就更新WifiConfiguration的参数(密码等参数),调用WifiManager的updateNetwork方法去更新这个wifi。上面两种方式,都会返回一个int值(the ID of the network),如果大于0,则表示操作成功;小于0表示操作失败。
第一步,查看google官方的6.0 changes文档,看是否能找出很直观的原因。Android 6.0 Changes链接
Wi-Fi and Networking Changes
This release introduces the following behavior changes to the Wi-Fi and networking APIs.
- Your apps can now change the state of WifiConfiguration objects only if you created these objects. You are not permitted to modify or delete WifiConfiguration objects created by the user or by other apps.
google已经明确的告诉开发者,APP是不能修改或者删除不是自己创建的wifi的。接下来,从源代码层面深入分析一下原理。
第二步,对比Android6.0和5.0以及7.0的源代码,查看版本之间的差异。
WifiManager里,都会调用到WifiServiceImpl的addOrUpdateNetwork方法。
private int addOrUpdateNetwork(WifiConfiguration config) {
try {
return mService.addOrUpdateNetwork(config);
} catch (RemoteException e) {
return -1;
}
}
然后,进入到WifiServiceImpl里面,进行一系列的权限验证后,为方法参数config设置当前uid的信息后,才开始链接
if (config.networkId == WifiConfiguration.INVALID_NETWORK_ID) {
config.creatorUid = Binder.getCallingUid();
} else {
config.lastUpdateUid = Binder.getCallingUid();
}
if (mWifiStateMachineChannel != null) {
return mWifiStateMachine.syncAddOrUpdateNetwork(mWifiStateMachineChannel, config);
} else {
Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
return -1;
}
继续往状态机WifiStateMachine里走–
public int syncAddOrUpdateNetwork(AsyncChannel channel, WifiConfiguration config) {
Message resultMsg = channel.sendMessageSynchronously(CMD_ADD_OR_UPDATE_NETWORK, config);
int result = resultMsg.arg1;
resultMsg.recycle();
return result;
}
在内部类ConnectModeState的processMessage(Message message)方法里,开始处理消息CMD_ADD_OR_UPDATE_NETWORK,也就是这里,开始出现了版本代码的差异。
下面是6.0代码比5.0新增的代码片段
case CMD_ADD_OR_UPDATE_NETWORK:
config = (WifiConfiguration) message.obj;
// difference begin(6.0新增代码开始位置)
if (!recordUidIfAuthorized(config, message.sendingUid,
/* onlyAnnotate */ false)) {
logw("Not authorized to update network "
+ " config=" + config.SSID
+ " cnid=" + config.networkId
+ " uid=" + message.sendingUid);
replyToMessage(message, message.what, FAILURE);
break;
}
// difference end(6.0新增代码结束位置)
//......
重点看一下recordUidIfAuthorized()方法–
/**
* Save the UID correctly depending on if this is a new or existing network.
* @return true if operation is authorized, false otherwise
*/
boolean recordUidIfAuthorized(WifiConfiguration config, int uid, boolean onlyAnnotate) {
if (!mWifiConfigStore.isNetworkConfigured(config)) {
config.creatorUid = uid;
config.creatorName = mContext.getPackageManager().getNameForUid(uid);
} else if (!mWifiConfigStore.canModifyNetwork(uid, config, onlyAnnotate)) {
return false;
}
config.lastUpdateUid = uid;
config.lastUpdateName = mContext.getPackageManager().getNameForUid(uid);
return true;
}
1、首先通过WifiConfigStore对象判断如果这个wifi还没有被存储过,则记录creatorUid为当前的app id,这个比较好理解。
2、然后继续判断当前app有没有权限修改这个wifi,就是canModifyNetwork()方法。继续跟进去,最终会执行到WifiConfigStore的canModifyNetwork()方法–
/**
* Checks if uid has access to modify the configuration corresponding to networkId.
*
* Factors involved in modifiability of a config are as follows.
* If uid is a Device Owner app then it has full control over the device, including WiFi
* configs.
* If the modification is only for administrative annotation (e.g. when connecting) or the
* config is not lockdown eligible (currently that means any config not last updated by the DO)
* then the creator of config or an app holding OVERRIDE_CONFIG_WIFI can modify the config.
* If the config is lockdown eligible and the modification is substantial (not annotation)
* then the requirement to be able to modify the config by the uid is as follows:
* a) the uid has to hold OVERRIDE_CONFIG_WIFI and
* b) the lockdown feature should be disabled.
*/
boolean canModifyNetwork(int uid, int networkId, boolean onlyAnnotate) {
WifiConfiguration config = mConfiguredNetworks.get(networkId);
if (config == null) {
loge("canModifyNetwork: cannot find config networkId " + networkId);
return false;
}
final DevicePolicyManagerInternal dpmi = LocalServices.getService(
DevicePolicyManagerInternal.class);
final boolean isUidDeviceOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(uid,
DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
if (isUidDeviceOwner) {
// Device Owner has full control over the device, including WiFi Configs
return true;
}
final boolean isCreator = (config.creatorUid == uid);
if (onlyAnnotate) {
return isCreator || checkConfigOverridePermission(uid);
}
// Check if device has DPM capability. If it has and dpmi is still null, then we
// treat this case with suspicion and bail out.
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)
&& dpmi == null) {
return false;
}
// WiFi config lockdown related logic. At this point we know uid NOT to be a Device Owner.
final boolean isConfigEligibleForLockdown = dpmi != null && dpmi.isActiveAdminWithPolicy(
config.creatorUid, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
if (!isConfigEligibleForLockdown) {
return isCreator || checkConfigOverridePermission(uid);
}
final ContentResolver resolver = mContext.getContentResolver();
final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
return !isLockdownFeatureEnabled && checkConfigOverridePermission(uid);
}
这里面会先判断当前app是否是Device Owner,然后判断是否有权限OVERRIDE_WIFI_CONFIG,这个app都不符合,所以会返回false,分析到这里得以验证。
3、结语:
用google的原话:Your apps can now change the state of WifiConfiguration objects only if you created these objects. You are not permitted to modify or delete WifiConfiguration objects created by the user or by other apps.
4、备注:google在6.0上增加了这个逻辑,然后又在7.0去掉了。所以这个问题只存在于6.0的系统上。