Android SoftAp SoftAp打开/关闭代码流程(基于android 7.0)
2016-12-15 16:04
1731 查看
在Android手机中,SoftAp也是一个较为常用的功能。对于framework开发者来说,要开发、维护SoftAp,了解framework中热点开关的具体流程是非常有必要的。下面就对这部分内容做一些介绍,以供后续查阅。
一、SoftAp打开流程
当我们在设置中打开热点时,会调用WifiManager::setWifiApEnabled(),参数enabled为true;间接调用同名的WifiServiceImpl::setWifiApEnabled()
参数中的wifiConfig对象保存了在Settings中操作时保留的热点信息,如热点名称、密钥和加密方式等等。与Wifi本身的打开和关闭类似,Wifi热点的打开流程也是通过WifiController状态机向WifiStateMachine转发消息的。与前面介绍的Wifi打开流程类似,CMD_SET_AP消息在ApStaDisabledState状态处理.
由此转入WifiStateMachine进行打开流程
WifiStateMachine::InitialState会处理该消息
1. 第一个分支
首先肯定是先加载Driver,Driver加载成功Wifi热点需要绑定端口信息,再以AP模式通过NetworkManagementService在wlan0端口下加载固件;同时热点功能也需要HAL层的支持。setWifiApState()会发送广播,告知当前热点打开的过程信息;同理,也有setWifiState(),告知外界当前Wifi打开的过程信息;如果我们有必要知道当前热点打开的过程进行到什么阶段了,可以监听WifiManager.WIFI_AP_STATE_CHANGED_ACTION广播。最后状态切换到SoftApState,如果流程有误,则会重新进入InitialState。接着看SoftApState::enter()
writeApConfiguration()将mWifiApConfig的信息更新到/misc/wifi/softap.conf文件中
首先会判断打开热点时传入的WifiConfiguration对象是否为null;如果为空,则会向WifiApConfigStore获取默认的Config或者已配置的config,如果上层配置了热点;我们也会将新的配置信息更新到softap.conf中,以供下次再次加载。
2. 第二个分支
我们一起介绍这两个分支过程。对于CMD_START_AP消息的处理再看第二个分支,InitialState状态的enter()
主要是从/misc/wifi/softap.conf文件中读取其中的信息,并赋给WifiApConfigStore的成员变量mWifiApConfig,这个变量保存的就是当前SoftAp的配置信息。该文件一开始会有默认的信息保存其中,如果我们从没配置过热点,拿到的就是系统默认的信息;然后会重新去处理CMD_START_AP消息.就走到了第一个分支的路径.
经过这么一番折腾,目的其实就是为了获取一个合法的WifiApConfig,获取到之后终于就是启动SoftAp了.接下来进入mSoftApManager.start(config);处理.
在SoftApManager.java文件中的IdleState的状态中处理CMD_START消息
启动SoftAp继续看startSoftAp方法
如下是设置信道和国家码的方法.
经过信道和国家吗信息的更新终于要进入Netd启动SoftAp了.在NetworkManagementService.java文件中的startAccessPoint中进行处理.代码如下
从这个方法中可以看出最终就是执行executeOrLogWithMessage方法.
此之后就是在Netd进行SotAp的开启操作."startap"命令会在CommandListener.cpp文件中的SoftapCmd::runCommand函数进行处理.并且调用startSoftap()函数,进行继续开启SoftAp的操作.并且会将执行的结果通过发送消息的方式将结果返回.
在SoftapController.cpp文件中的startSoftap()函数中会启动Hostapd进程.根据/data/misc/wifi/hostapd.conf文件 /data/misc/wifi/entropy.bin文件启动/system/bin/hostapd进程.至此Softap开启完成.
到这里,一个完整的SoftAp打开流程就结束了。
二、SoftAp关闭流程
关闭SoftAp的方法调用与打开SoftAp一致,不过enabled此时是为false.
由第一部分的内容可知WifiController状态机在处理完SoftAp打开后,停在WifiController.java文件中的ApEnabledState状态,那么我们看它是怎么处理CMD_SET_AP消息
有前述可知,如果参数enabled为false,mag.arg1就应该为0,调用setHostApRunning()走关闭流程,并将WifiController中的状态重置为ApStaDisabledState,等待下一次流程的开始。看setHostApRunning()
发送CMD_STOP_AP消息;已知SoftAp成功打开后,WifiStateMachine停留在SoftApState,看其处理
发送CMD_STOP消息;已知Softap成功开启之后,SoftApManager状态机停留在StartedState状态.看其处理
从上代码可以看出真正关闭SoftAp的代码为stopSoftAp().看其实现如下:
代码也由此进入了Netd进行处理.接下来看一下stopAccessPoint()函数.NetworkManagementService文件中的处理如下
从上面的代码可以看出,SoftAp关闭过程中要执行的命令为"stopap".然后通过Socket发送出去.下面看一下通过Socket发送"stopap"命令的实现.
由此通过execute方法将命令"stopap"通过Socket发送出去.在CommandListener.cpp文件中监听"stopap"命令.看其实现
当CommandListener.cpp文件中监听到opap"命令时,会调用SoftapController.cpp文件中的stopSoftap()函数进行实现.
此函数中通过kill命令杀死Hostapd进程.至此SoftAp关闭成功.奇怪的是在Android 7.0上没有卸载SoftAp的驱动吗?
一、SoftAp打开流程
当我们在设置中打开热点时,会调用WifiManager::setWifiApEnabled(),参数enabled为true;间接调用同名的WifiServiceImpl::setWifiApEnabled()
/** * Start AccessPoint mode with the specified * configuration. If the radio is already running in * AP mode, update the new configuration * Note that starting in access point mode disables station * mode operation * @param wifiConfig SSID, security and channel details as * part of WifiConfiguration * @return {@code true} if the operation succeeds, {@code false} otherwise * * @hide */ @SystemApi public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) { try { mService.setWifiApEnabled(wifiConfig, enabled); return true; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
/** * see {@link android.net.wifi.WifiManager#setWifiApEnabled(WifiConfiguration, boolean)} * @param wifiConfig SSID, security and channel details as * part of WifiConfiguration * @param enabled true to enable and false to disable */ public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) { enforceChangePermission(); ConnectivityManager.enforceTetherChangePermission(mContext); if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) { throw new SecurityException("DISALLOW_CONFIG_TETHERING is enabled for this user."); } // null wifiConfig is a meaningful input for CMD_SET_AP if (wifiConfig == null || isValid(wifiConfig)) { mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget(); } else { Slog.e(TAG, "Invalid WifiConfiguration"); } }
参数中的wifiConfig对象保存了在Settings中操作时保留的热点信息,如热点名称、密钥和加密方式等等。与Wifi本身的打开和关闭类似,Wifi热点的打开流程也是通过WifiController状态机向WifiStateMachine转发消息的。与前面介绍的Wifi打开流程类似,CMD_SET_AP消息在ApStaDisabledState状态处理.
case CMD_SET_AP: if (msg.arg1 == 1) { if (msg.arg2 == 0) { // previous wifi state has not been saved yet mSettingsStore.setWifiSavedState(WifiSettingsStore.WIFI_DISABLED); } mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj, true); transitionTo(mApEnabledState); } break;
由此转入WifiStateMachine进行打开流程
/** * TODO: doc */ public void setHostApRunning(WifiConfiguration wifiConfig, boolean enable) { if (enable) { sendMessage(CMD_START_AP, wifiConfig); } else { sendMessage(CMD_STOP_AP); } }
WifiStateMachine::InitialState会处理该消息
case CMD_START_AP: if (setupDriverForSoftAp()) { //1. 第一个分支 transitionTo(mSoftApState); } else { setWifiApState(WIFI_AP_STATE_FAILED, WifiManager.SAP_START_FAILURE_GENERAL); /** * Transition to InitialState (current state) to reset the * driver/HAL back to the initial state. */ transitionTo(mInitialState); //2. 第二个分支 } break;
1. 第一个分支
/* Driver/firmware setup for soft AP. */ private boolean setupDriverForSoftAp() { if (!mWifiNative.loadDriver()) { //加载驱动 Log.e(TAG, "Failed to load driver for softap"); return false; } int index = mWifiNative.queryInterfaceIndex(mInterfaceName); //绑定接口 if (index != -1) { if (!mWifiNative.setInterfaceUp(false)) { //启动接口 Log.e(TAG, "toggleInterface failed"); return false; } else { if (DBG) Log.d(TAG, "No interfaces to bring down"); } try { mNwService.wifiFirmwareReload(mInterfaceName, "AP"); //加载Firmware if (DBG) Log.d(TAG, "Firmware reloaded in AP mode"); } catch (Exception e) { Log.e(TAG, "Failed to reload AP firmware " + e); } if (!mWifiNative.startHal()) { //启动HAL /* starting HAL is optional */ Log.e(TAG, "Failed to start HAL"); } return true; }
首先肯定是先加载Driver,Driver加载成功Wifi热点需要绑定端口信息,再以AP模式通过NetworkManagementService在wlan0端口下加载固件;同时热点功能也需要HAL层的支持。setWifiApState()会发送广播,告知当前热点打开的过程信息;同理,也有setWifiState(),告知外界当前Wifi打开的过程信息;如果我们有必要知道当前热点打开的过程进行到什么阶段了,可以监听WifiManager.WIFI_AP_STATE_CHANGED_ACTION广播。最后状态切换到SoftApState,如果流程有误,则会重新进入InitialState。接着看SoftApState::enter()
public void enter() { final Message message = getCurrentMessage(); if (message.what == CMD_START_AP) { WifiConfiguration config = (WifiConfiguration) message.obj; if (config == null) { /** * Configuration not provided in the command, fallback to use the current * configuration. */ config = mWifiApConfigStore.getApConfiguration(); //获取一个Config,不一定是默认的,有可能是之前保存下去的. } else { /* Update AP configuration. */ mWifiApConfigStore.setApConfiguration(config); //将Config文件备份到之前的Config中 } checkAndSetConnectivityInstance(); //这方法就是获取一个Connectivity服务 mSoftApManager = mFacade.makeSoftApManager( mContext, getHandler().getLooper(), mWifiNative, mNwService, mCm, mCountryCode.getCurrentCountryCode(), mWifiApConfigStore.getAllowed2GChannel(), new SoftApListener()); mSoftApManager.start(config); //根据配置文件启动SoftAp } else { throw new RuntimeException("Illegal transition to SoftApState: " + message); } }
/** * Update the current soft access point configuration. * Restore to default AP configuration if null is provided. * This can be invoked under context of binder threads (WifiManager.setWifiApConfiguration) * and WifiStateMachine thread (CMD_START_AP). */ public synchronized void setApConfiguration(WifiConfiguration config) { if (config == null) { mWifiApConfig = getDefaultApConfiguration(); //获取默认的ApConfig } else { mWifiApConfig = config; } //将得到的ApConfig(默认或者已配置的config)备份到另外一个配置文件中. //这样做是确保下次开启时能够获取到上次保存的配置.如果有新的Config将会更新备份中的config. writeApConfiguration(mApConfigFile, mWifiApConfig); // Stage the backup of the SettingsProvider package which backs this up mBackupManagerProxy.notifyDataChanged(); }
writeApConfiguration()将mWifiApConfig的信息更新到/misc/wifi/softap.conf文件中
/** * Write AP configuration to persistent storage. */ private static void writeApConfiguration(final String filename, final WifiConfiguration config) { try {DataOutputStream out = new DataOutputStream(new BufferedOutputStream( new FileOutputStream(filename)))) { out.writeInt(AP_CONFIG_FILE_VERSION); out.writeUTF(config.SSID); out.writeInt(config.apBand); out.writeInt(config.apChannel); //NOTE: Bug #474462 Add For SoftAp advance Feature BEG--> out.writeInt(config.softApMaxNumSta); //<-- Add for SoftAp Advance Feature END int authType = config.getAuthType(); out.writeInt(authType); if (authType != KeyMgmt.NONE) { out.writeUTF(config.preSharedKey); } } catch (IOException e) { Log.e(TAG, "Error writing hotspot configuration" + e); } }
/** * Generate a default WPA2 based configuration with a random password. * We are changing the Wifi Ap configuration storage from secure settings to a * flat file accessible only by the system. A WPA2 based default configuration * will keep the device secure after the update. */ private WifiConfiguration getDefaultApConfiguration() { WifiConfiguration config = new WifiConfiguration(); config.SSID = mContext.getResources().getString( R.string.wifi_tether_configure_ssid_default); config.allowedKeyManagement.set(KeyMgmt.WPA2_PSK); String randomUUID = UUID.randomUUID().toString(); //first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx config.preSharedKey = randomUUID.substring(0, 8) + randomUUID.substring(9, 13); config.softApMaxNumSta = 8; config.apBand = 0; config.apChannel = 11; return config; } }
首先会判断打开热点时传入的WifiConfiguration对象是否为null;如果为空,则会向WifiApConfigStore获取默认的Config或者已配置的config,如果上层配置了热点;我们也会将新的配置信息更新到softap.conf中,以供下次再次加载。
2. 第二个分支
我们一起介绍这两个分支过程。对于CMD_START_AP消息的处理再看第二个分支,InitialState状态的enter()
class InitialState extends State { @Override public void enter() { mWifiNative.stopHal(); mWifiNative.unloadDriver(); if (mWifiP2pChannel == null) { mWifiP2pChannel = new AsyncChannel(); mWifiP2pChannel.connect(mContext, getHandler(),mWifiP2pServiceImpl.getP2pStateMachineMessenger()); } if (mWifiApConfigStore == null) { //WifiApConfigStore也是一个小的状态机,此时会构建mWifiApConfigStore对戏,并启动状态机 mWifiApConfigStore = mFacade.makeApConfigStore(mContext, mBackupManagerProxy); } } ...... }
主要是从/misc/wifi/softap.conf文件中读取其中的信息,并赋给WifiApConfigStore的成员变量mWifiApConfig,这个变量保存的就是当前SoftAp的配置信息。该文件一开始会有默认的信息保存其中,如果我们从没配置过热点,拿到的就是系统默认的信息;然后会重新去处理CMD_START_AP消息.就走到了第一个分支的路径.
经过这么一番折腾,目的其实就是为了获取一个合法的WifiApConfig,获取到之后终于就是启动SoftAp了.接下来进入mSoftApManager.start(config);处理.
/** * Start soft AP with given configuration. * @param config AP configuration */ public void start(WifiConfiguration config) { mStateMachine.sendMessage(SoftApStateMachine.CMD_START, config); }
在SoftApManager.java文件中的IdleState的状态中处理CMD_START消息
case CMD_START: updateApState(WifiManager.WIFI_AP_STATE_ENABLING, 0); int result = startSoftAp((WifiConfiguration) message.obj); //启动SoftAp if (result == SUCCESS) { updateApState(WifiManager.WIFI_AP_STATE_ENABLED, 0); //启动成功之后更新Ap的状态 transitionTo(mStartedState); } else { int reason = WifiManager.SAP_START_FAILURE_GENERAL; if (result == ERROR_NO_CHANNEL) { reason = WifiManager.SAP_START_FAILURE_NO_CHANNEL; } updateApState(WifiManager.WIFI_AP_STATE_FAILED, reason); //如果启动失败将会更新失败的消息,并且附带原因 } break;
启动SoftAp继续看startSoftAp方法
/** * Start a soft AP instance with the given configuration. * @param config AP configuration * @return integer result code */ private int startSoftAp(WifiConfiguration config) { if (config == null) { Log.e(TAG, "Unable to start soft AP without configuration"); return ERROR_GENERIC; } /* Make a copy of configuration for updating AP band and channel. */ WifiConfiguration localConfig = new WifiConfiguration(config); int result = ApConfigUtil.updateApChannelConfig( mWifiNative, mCountryCode, mAllowed2GChannels, localConfig); //根据配置文件更新国家码和信道信息 if (result != SUCCESS) { Log.e(TAG, "Failed to update AP band and channel"); return result; } /* Setup country code if it is provide. */ if (mCountryCode != null) { /** * Country code is mandatory for 5GHz band, return an error if failed to set * country code when AP is configured for 5GHz band. */ if (!mWifiNative.setCountryCodeHal(mCountryCode.toUpperCase(Locale.ROOT)) //设置国家码 && config.apBand == WifiConfiguration.AP_BAND_5GHZ) { Log.e(TAG, "Failed to set country code, required for setting up " + "soft ap in 5GHz"); return ERROR_GENERIC; } } try { mNmService.startAccessPoint(localConfig, mInterfaceName); //通过NetworkManagerService,在无线端口上,按传入的配置信息开启SoftAP; } catch (Exception e) { Log.e(TAG, "Exception in starting soft AP: " + e); return ERROR_GENERIC; } Log.d(TAG, "Soft AP is started"); return SUCCESS; }
如下是设置信道和国家码的方法.
/** * Update AP band and channel based on the provided country code and band. * This will also set * @param wifiNative reference to WifiNative * @param countryCode country code * @param allowed2GChannels list of allowed 2GHz channels * @param config configuration to update * @return an integer result code */ public static int updateApChannelConfig(WifiNative wifiNative, String countryCode, ArrayList<Integer> allowed2GChannels, WifiConfiguration config) { /* Use default band and channel for device without HAL. */ if (!wifiNative.isHalStarted()) { //因为SoftAp需要HAL层的支持,所有首先要进行确定,再继续配置 config.apBand = DEFAULT_AP_BAND; //config.apChannel = DEFAULT_AP_CHANNEL; if (config.apChannel <= 0 || config.apChannel > 14) config.apChannel = DEFAULT_AP_CHANNEL; return SUCCESS; } /* Country code is mandatory for 5GHz band. */ if (config.apBand == WifiConfiguration.AP_BAND_5GHZ && countryCode == null) { Log.e(TAG, "5GHz band is not allowed without country code"); return ERROR_GENERIC; } /* Select a channel if it is not specified. */ if (config.apChannel == 0) { config.apChannel = chooseApChannel( config.apBand, allowed2GChannels, wifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ)); if (config.apChannel == -1) { if (wifiNative.isGetChannelsForBandSupported()) { /* We're not able to get channel when it is supported by HAL. */ Log.e(TAG, "Failed to get available channel."); return ERROR_NO_CHANNEL; } /* Use the default for HAL without get channel support. */ config.apBand = DEFAULT_AP_BAND; config.apChannel = DEFAULT_AP_CHANNEL; } } return SUCCESS; }
经过信道和国家吗信息的更新终于要进入Netd启动SoftAp了.在NetworkManagementService.java文件中的startAccessPoint中进行处理.代码如下
public void startAccessPoint(WifiConfiguration wifiConfig, String wlanIface) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); Object[] args; String logMsg = "startAccessPoint Error setting up softap"; try { if (wifiConfig == null) { args = new Object[] {"set", wlanIface}; } else { args = new Object[] {"set", wlanIface, wifiConfig.SSID, //"broadcast", Integer.toString(wifiConfig.apChannel), (wifiConfig.hiddenSSID?"hidden":"broadcast"), Integer.toString(wifiConfig.apChannel), //getSecurityType(wifiConfig), new SensitiveArg(wifiConfig.preSharedKey)}; getSecurityType(wifiConfig), new SensitiveArg(wifiConfig.preSharedKey), wifiConfig.softApMaxNumSta}; } executeOrLogWithMessage(SOFT_AP_COMMAND, args, NetdResponseCode.SoftapStatusResult, SOFT_AP_COMMAND_SUCCESS, logMsg); logMsg = "startAccessPoint Error starting softap"; args = new Object[] {"startap"}; executeOrLogWithMessage(SOFT_AP_COMMAND, args, NetdResponseCode.SoftapStatusResult, SOFT_AP_COMMAND_SUCCESS, logMsg); } catch (NativeDaemonConnectorException e) { throw e.rethrowAsParcelableException(); } }
从这个方法中可以看出最终就是执行executeOrLogWithMessage方法.
/** * Private method used to call execute for a command given the provided arguments. * * This function checks the returned NativeDaemonEvent for the provided expected response code * and message. If either of these is not correct, an error is logged. * * @param String command The command to execute. * @param Object[] args If needed, arguments for the command to execute. * @param int expectedResponseCode The code expected to be returned in the corresponding event. * @param String expectedResponseMessage The message expected in the returned event. * @param String logMsg The message to log as an error (TAG will be applied). */ private void executeOrLogWithMessage(String command, Object[] args, int expectedResponseCode, String expectedResponseMessage, String logMsg) throws NativeDaemonConnectorException { NativeDaemonEvent event = mConnector.execute(command, args); if (event.getCode() != expectedResponseCode || !event.getMessage().equals(expectedResponseMessage)) { Log.e(TAG, logMsg + ": event = " + event); } }
此之后就是在Netd进行SotAp的开启操作."startap"命令会在CommandListener.cpp文件中的SoftapCmd::runCommand函数进行处理.并且调用startSoftap()函数,进行继续开启SoftAp的操作.并且会将执行的结果通过发送消息的方式将结果返回.
int CommandListener::SoftapCmd::runCommand(SocketClient *cli, int argc, char **argv) { int rc = ResponseCode::SoftapStatusResult; char *retbuf = NULL; if (gCtls == nullptr) { cli->sendMsg(ResponseCode::ServiceStartFailed, "SoftAP is not available", false); return -1; } if (argc < 2) { cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument in a SoftAP command", false); return 0; } if (!strcmp(argv[1], "startap")) { rc = gCtls->softapCtrl.startSoftap(); //开启SoftAp } else if (!strcmp(argv[1], "stopap")) { rc = gCtls->softapCtrl.stopSoftap(); } else if (!strcmp(argv[1], "fwreload")) { rc = gCtls->softapCtrl.fwReloadSoftap(argc, argv); } else if (!strcmp(argv[1], "status")) { asprintf(&retbuf, "Softap service %s running", (gCtls->softapCtrl.isSoftapStarted() ? "is" : "is not")); cli->sendMsg(rc, retbuf, false); free(retbuf); return 0; } else if (!strcmp(argv[1], "set")) { rc = gCtls->softapCtrl.setSoftap(argc, argv); #ifdef CONFIG_HOSTAPD_ADVANCE } else if (!strcmp(argv[1], "whitelist")) {//add for change softap white list enable if ((argc >= 3) && (!strcmp(argv[2], "true"))) { //enable white list rc =gCtls->softapCtrl.setSoftapWhiteListEnable(true); } else { rc = gCtls->softapCtrl.setSoftapWhiteListEnable(false); } #endif } else { cli->sendMsg(ResponseCode::CommandSyntaxError, "Unrecognized SoftAP command", false); return 0; } if (rc >= 400 && rc < 600) //此处为返回SoftAp的执行结果.成功此值为214.异常为402 cli->sendMsg(rc, "SoftAP command has failed", false); else cli->sendMsg(rc, "Ok", false); return 0; }
在SoftapController.cpp文件中的startSoftap()函数中会启动Hostapd进程.根据/data/misc/wifi/hostapd.conf文件 /data/misc/wifi/entropy.bin文件启动/system/bin/hostapd进程.至此Softap开启完成.
int SoftapController::startSoftap() { pid_t pid = 1; if (mPid) { ALOGE("SoftAP is already running"); return ResponseCode::SoftapStatusResult; } if (ensure_entropy_file_exists() < 0) { ALOGE("Wi-Fi entropy file was not created"); } if ((pid = fork()) < 0) { ALOGE("fork failed (%s)", strerror(errno)); return ResponseCode::ServiceStartFailed; } if (!pid) { ensure_entropy_file_exists(); if (execl(HOSTAPD_BIN_FILE, HOSTAPD_BIN_FILE, "-e", WIFI_ENTROPY_FILE, HOSTAPD_CONF_FILE, (char *) NULL)) { ALOGE("execl failed (%s)", strerror(errno)); } ALOGE("SoftAP failed to start"); return ResponseCode::ServiceStartFailed; } else { mPid = pid; ALOGD("SoftAP started successfully"); //此处证明SoftAp启动成功 usleep(AP_BSS_START_DELAY); } return ResponseCode::SoftapStatusResult; }
到这里,一个完整的SoftAp打开流程就结束了。
二、SoftAp关闭流程
关闭SoftAp的方法调用与打开SoftAp一致,不过enabled此时是为false.
/** * Start AccessPoint mode with the specified * configuration. If the radio is already running in * AP mode, update the new configuration * Note that starting in access point mode disables station * mode operation * @param wifiConfig SSID, security and channel details as * part of WifiConfiguration * @return {@code true} if the operation succeeds, {@code false} otherwise * * @hide */ @SystemApi public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) { try { mService.setWifiApEnabled(wifiConfig, enabled); return true; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
由第一部分的内容可知WifiController状态机在处理完SoftAp打开后,停在WifiController.java文件中的ApEnabledState状态,那么我们看它是怎么处理CMD_SET_AP消息
case CMD_SET_AP: if (msg.arg1 == 0) { mWifiStateMachine.setHostApRunning(null, false); //在WifiStateMachine中开始热点关闭流程 mPendingState = getNextWifiState(); //切换到初始状态 } break;
有前述可知,如果参数enabled为false,mag.arg1就应该为0,调用setHostApRunning()走关闭流程,并将WifiController中的状态重置为ApStaDisabledState,等待下一次流程的开始。看setHostApRunning()
/** * TODO: doc */ public void setHostApRunning(WifiConfiguration wifiConfig, boolean enable) { if (enable) { sendMessage(CMD_START_AP, wifiConfig); } else { sendMessage(CMD_STOP_AP); } }
发送CMD_STOP_AP消息;已知SoftAp成功打开后,WifiStateMachine停留在SoftApState,看其处理
case CMD_STOP_AP: mSoftApManager.stop(); //直接关闭SoftAp break;
/** * Stop soft AP. */ public void stop() { mStateMachine.sendMessage(SoftApStateMachine.CMD_STOP); }
发送CMD_STOP消息;已知Softap成功开启之后,SoftApManager状态机停留在StartedState状态.看其处理
case CMD_STOP: updateApState(WifiManager.WIFI_AP_STATE_DISABLING, 0); //更新关闭SoftAp的状态为关闭中 stopSoftAp(); //停止SoftAp updateApState(WifiManager.WIFI_AP_STATE_DISABLED, 0); //更新关闭SoftAp的状态为已关闭 transitionTo(mIdleState); mConnectedStations = ""; if(mConnectedToHostapd) { if (DBG) log("Stopping Soft AP Monitor"); mWifiMonitor.stopSoftApMonitoring(mInterfaceName); } if (DBG) log("WifiMonitor: Close to hostapd sockets!"); mWifiNative.closeHostapdConnection(); //关闭Hostapd的关联 mConnectedToHostapd = false; unregisterSoftapEventHandler(); //取消注册SoftAp的事件 /* quit(); */ } break;
从上代码可以看出真正关闭SoftAp的代码为stopSoftAp().看其实现如下:
/** * Teardown soft AP. */ private void stopSoftAp() { try { mNmService.stopAccessPoint(mInterfaceName); } catch (Exception e) { Log.e(TAG, "Exception in stopping soft AP: " + e); return; } Log.d(TAG, "Soft AP is stopped"); }
代码也由此进入了Netd进行处理.接下来看一下stopAccessPoint()函数.NetworkManagementService文件中的处理如下
public void stopAccessPoint(String wlanIface) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); Object[] args = {"stopap"}; //要执行的命令 String logMsg = "stopAccessPoint Error stopping softap"; try { executeOrLogWithMessage(SOFT_AP_COMMAND, args, NetdResponseCode.SoftapStatusResult, SOFT_AP_COMMAND_SUCCESS, logMsg);//将命令通过Socket发送出去 wifiFirmwareReload(wlanIface, "STA"); } catch (NativeDaemonConnectorException e) { throw e.rethrowAsParcelableException(); } }
从上面的代码可以看出,SoftAp关闭过程中要执行的命令为"stopap".然后通过Socket发送出去.下面看一下通过Socket发送"stopap"命令的实现.
/** * Private method used to call execute for a command given the provided arguments. * * This function checks the returned NativeDaemonEvent for the provided expected response code * and message. If either of these is not correct, an error is logged. * * @param String command The command to execute. * @param Object[] args If needed, arguments for the command to execute. * @param int expectedResponseCode The code expected to be returned in the corresponding event. * @param String expectedResponseMessage The message expected in the returned event. * @param String logMsg The message to log as an error (TAG will be applied). */ private void executeOrLogWithMessage(String command, Object[] args, int expectedResponseCode, String expectedResponseMessage, String logMsg) throws NativeDaemonConnectorException { NativeDaemonEvent event = mConnector.execute(command, args); if (event.getCode() != expectedResponseCode || !event.getMessage().equals(expectedResponseMessage)) { Log.e(TAG, logMsg + ": event = " + event); } }
由此通过execute方法将命令"stopap"通过Socket发送出去.在CommandListener.cpp文件中监听"stopap"命令.看其实现
CommandListener::SoftapCmd::SoftapCmd() : NetdCommand("softap") { } int CommandListener::SoftapCmd::runCommand(SocketClient *cli, int argc, char **argv) { int rc = ResponseCode::SoftapStatusResult; char *retbuf = NULL; if (gCtls == nullptr) { cli->sendMsg(ResponseCode::ServiceStartFailed, "SoftAP is not available", false); return -1; } if (argc < 2) { cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument in a SoftAP command", false); return 0; } if (!strcmp(argv[1], "startap")) { rc = gCtls->softapCtrl.startSoftap(); } else if (!strcmp(argv[1], "stopap")) { rc = gCtls->softapCtrl.stopSoftap(); //关闭SoftAp } else if (!strcmp(argv[1], "fwreload")) { rc = gCtls->softapCtrl.fwReloadSoftap(argc, argv); } else if (!strcmp(argv[1], "status")) { asprintf(&retbuf, "Softap service %s running", (gCtls->softapCtrl.isSoftapStarted() ? "is" : "is not")); cli->sendMsg(rc, retbuf, false); free(retbuf); return 0; } else if (!strcmp(argv[1], "set")) { rc = gCtls->softapCtrl.setSoftap(argc, argv); #ifdef CONFIG_HOSTAPD_ADVANCE } else if (!strcmp(argv[1], "whitelist")) {//add for change softap white list enable if ((argc >= 3) && (!strcmp(argv[2], "true"))) { //enable white list rc =gCtls->softapCtrl.setSoftapWhiteListEnable(true); } else { rc = gCtls->softapCtrl.setSoftapWhiteListEnable(false); } #endif } else { cli->sendMsg(ResponseCode::CommandSyntaxError, "Unrecognized SoftAP command", false); return 0; } if (rc >= 400 && rc < 600) cli->sendMsg(rc, "SoftAP command has failed", false); else cli->sendMsg(rc, "Ok", false); return 0; }
当CommandListener.cpp文件中监听到opap"命令时,会调用SoftapController.cpp文件中的stopSoftap()函数进行实现.
int SoftapController::stopSoftap() { if (mPid == 0) { ALOGE("SoftAP is not running"); return ResponseCode::SoftapStatusResult; } ALOGD("Stopping the SoftAP service..."); kill(mPid, SIGTERM); waitpid(mPid, NULL, 0); mPid = 0; ALOGD("SoftAP stopped successfully"); usleep(AP_BSS_STOP_DELAY); return ResponseCode::SoftapStatusResult; //此值为214,返回为214时代表stopSoftap()执行成功 }
此函数中通过kill命令杀死Hostapd进程.至此SoftAp关闭成功.奇怪的是在Android 7.0上没有卸载SoftAp的驱动吗?
相关文章推荐
- Android基于配置文件的测试模式(不修改代码通过配置文件来打开和关闭测试模式)
- Android 监听 Android中监听系统网络连接打开或者关闭的实现代码
- Android中打开扬声器关闭麦克风的代码实现
- 【笔记】Android在代码中打开或关闭WiFi
- android代码控制软键盘的打开与关闭的工具类(KeybordS)
- React Native:调用Android原生代码打开和关闭手电筒
- Android: wifi打开和关闭的流程解析
- android使用代码实现gps,gprs,wifi打开与关闭
- android中如何用代码来关闭打开的相机
- Android中监听系统网络连接打开或者关闭的实现代码
- Android 在代码中设置打开关闭GPS不必用户操作
- android 7.0 camera 打开流程
- Android -- Wifi热点的打开与关闭流程简介
- Android通过代码打开和关闭网络连接
- Android EditText 代码实现弹出打开和关闭工具类
- android代码控制软键盘的打开与关闭的工具类(KeybordS)
- Android代码自动打开和关闭软键盘
- Android中打开扬声器关闭麦克风的代码实现
- android 7.0 camera 打开流程
- Android: wifi打开和关闭的流程解析