多个应用连接wifi热点的切换问题--WifiConfiguration的添加--Android M


一、版本适配问题。在Android6.0上,APP无法更新系统保存过的、不是由当前APP创建的WIFI配置。

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的系统上。

以上内容转载自:https://blog.csdn.net/d510779720/article/details/53067989

-----------------------------------------------------------------------------------------------------------------------------

一下内容为补充和总结:

补充分析以下方法中的WifiConfigStore.isNetworkConfigured()

/**
 * 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;

}

看看isNetworkConfigured(),返回是否已经配置过,怎么才算配置过呢:

boolean isNetworkConfigured(WifiConfiguration config) {
        // Check if either we have a network Id or a WifiConfiguration
        // matching the one we are trying to add.
//此时还没有为该config生成networkId,如果是该config是从WifiManager中拿过来的,那么networkid不为-1,那么就看已配置的config列表中有没有该config
        if(config.networkId != INVALID_NETWORK_ID) {
            return (mConfiguredNetworks.get(config.networkId) != null);
        }
//如果该config的networkid为-1,那么看已配置config列表中有没有和该config的ssid和加密方式同时相同的config,看configKey方法,知道这个string是ssid和加密方式的组合
        return (mConfiguredNetworks.getByConfigKey(config.configKey()) != null);
    }

再补充分析一下WifiConfigStore#canModifyNetwork(int uid, int networkId, boolean onlyAnnotate):

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);//是否该进程对应的应用首次添加这个config的,如果是就可以修改

        if (isUidDeviceOwner) {
            // Device Owner has full control over the device, including WiFi Configs
            return true;
        }

        final boolean isCreator = (config.creatorUid == uid);//是否该进程对应的应用首次添加这个config的,如果是就可以修改
//如果是重大修改,即onlyAnnotate为false,那么有OVERRIDE_WIFI_PERMISSION也不一定可以修改,再往下看
        if (onlyAnnotate) {//是否只对config做一些类似注释的小改动(非重大改动),如果是只要是该应用添加的,或者有android.permission.OVERRIDE_WIFI_CONFIG权限
            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.
//跟Android M的新feature Wifi config lockdown有关,后面会说说这个特性
        final boolean isConfigEligibleForLockdown = dpmi != null && dpmi.isActiveAdminWithPolicy(
                config.creatorUid, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
        if (!isConfigEligibleForLockdown) {
            return isCreator || checkConfigOverridePermission(uid);
        }
//这里也是跟前面说的特性有关,这个特性有关,在settingsprovider中可以配置一个字段,为1,则启动lockdown,这样就算有OVERRIDE_WIFI_CONFIG权限也无法修改
        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);
    }

所以对于连接Wifi,最好是通过WifiManger#getConfiguredNetworks()得到一个List<WifiConfiguration>列表。然后逐个对比,看有没有对应ssid的config,有的话直接使用该config.networkId。但是添加到WifiService中的WifiConfiguration,并不一定是正确的,就是说这些config并不一定连接过,就是连接失败的也会保存在那里,除非你在他连接失败后主动把他移除了,所以用该方法去连接也有风险。


权限:<uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" />


WiFi configuration lockdown 是 Android M, android for work 新增加的功能,是指企业指定的wifi configuration不允许一般用户去修改。

如果有MTK online的账号可以访问这个地址: https://onlinesso.mediatek.com/Pages/eCourse.aspx?001=003&002=003002&003=003002001&itemId=846&csId=%257B433b9ec7-cc31-43c3-938c-6dfd42cf3b57%257D%2540%257Bad907af8-9a88-484a-b020-ea10437dadf8%257D



猜你喜欢

转载自blog.csdn.net/b1480521874/article/details/80246131
M
^M
今日推荐