(一百三十五)Android O探索WLAN扫描(WIFI SCAN ALWAYS)

1.界面信息

小米mix2 WLAN扫描开关位于设置-更多设置-系统安全-位置信息-扫描,截图如下

 

2.流程梳理

搜索字符串

jiatai@jiatai:~/expand/aosp/aosp/packages/apps/Settings/res/values-zh-rCN$ grep "WLAN.*扫描" ./ -nr
./arrays.xml:250:    "WLAN扫描"
./strings.xml:738:    "为了提高位置信息的精确度,系统应用和服务仍然会扫描 WLAN 网络。您可以在LINK_BEGIN扫描设置LINK_END中更改此设置。"
./strings.xml:739:    "要提高位置信息的精确度,请在LINK_BEGIN扫描设置LINK_END中开启 WLAN 扫描功能。"
./strings.xml:827:    "为了提高位置信息精确度以及其他目的,“%1$s”请求启用网络扫描功能(在关闭了WLAN时也可进行扫描)。\n\n是否对所有需要进行扫描的应用批准这项请求?"
./strings.xml:1430:    "WLAN 扫描"


jiatai@jiatai:~/expand/aosp/aosp/packages/apps/Settings/res/xml$ grep "location_scanning_wifi_always_scanning_title" ./ -nr
./location_scanning.xml:22:            android:title="@string/location_scanning_wifi_always_scanning_title"
jiatai@jiatai:~/expand/aosp/aosp/packages/apps/Settings/res/xml$ vim ./location_scanning.xml
jiatai@jiatai:~/expand/aosp/aosp/packages/apps/Settings/res/xml$ cd ../../src/
jiatai@jiatai:~/expand/aosp/aosp/packages/apps/Settings/src$ grep "wifi_always_scanning" ./ -nr
./com/android/settings/location/ScanningSettings.java:32:    private static final String KEY_WIFI_SCAN_ALWAYS_AVAILABLE = "wifi_always_scanning";

之后定位到ScanningSettings这个类对应于开关的逻辑处理

2.1 ScanningSettings

 68     @Override
 69     public boolean onPreferenceTreeClick(Preference preference) {
 70         String key = preference.getKey();
 71         if (KEY_WIFI_SCAN_ALWAYS_AVAILABLE.equals(key)) {
 72             Global.putInt(getContentResolver(),
 73                     Global.WIFI_SCAN_ALWAYS_AVAILABLE,
 74                     ((SwitchPreference) preference).isChecked() ? 1 : 0);
 75         } else if (KEY_BLUETOOTH_SCAN_ALWAYS_AVAILABLE.equals(key)) {
 76             Global.putInt(getContentResolver(),
 77                     Global.BLE_SCAN_ALWAYS_AVAILABLE,
 78                     ((SwitchPreference) preference).isChecked() ? 1 : 0);
 79         } else {
 80             return super.onPreferenceTreeClick(preference);
 81         }
 82         return true;
 83     }

从如上代码可以看到该开关其实就是对应了一个数据库值(Global.WIFI_SCAN_ALWAYS_AVAILABLE)的变化

在framework搜索相关数据库监听

(一百三十五)Android O探索WLAN扫描(WIFI SCAN ALWAYS)_第1张图片

挨个看了下WifiServiceImpl中有对该数据库值(Global.WIFI_SCAN_ALWAYS_AVAILABLE)的监听

 

2.2 WifiServiceImpl

    /**
     * Observes settings changes to scan always mode.
     */
    private void registerForScanModeChange() {
        ContentObserver contentObserver = new ContentObserver(null) {
            @Override
            public void onChange(boolean selfChange) {
                mSettingsStore.handleWifiScanAlwaysAvailableToggled();
                mWifiController.sendMessage(CMD_SCAN_ALWAYS_MODE_CHANGED);
            }
        };
        mFrameworkFacade.registerContentObserver(mContext,
                Settings.Global.getUriFor(Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE),
                false, contentObserver);

    }

数据库值(Global.WIFI_SCAN_ALWAYS_AVAILABLE)的变化会触发

                mSettingsStore.handleWifiScanAlwaysAvailableToggled();
                mWifiController.sendMessage(CMD_SCAN_ALWAYS_MODE_CHANGED);

依次看下

 

2.3 WifiSettingsStore

    synchronized void handleWifiScanAlwaysAvailableToggled() {
        mScanAlwaysAvailable = getPersistedScanAlwaysAvailable();
    }

    private boolean getPersistedScanAlwaysAvailable() {
        return Settings.Global.getInt(mContext.getContentResolver(),
                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE,
                0) == 1;
    }

WifiSettingsStore就是更新一下自己保存的局部变量mScanAlwaysAvailable与当前开关保存一致。

 

2.4 WifiController

搜索代码看了下只有三个状态会对消息CMD_SCAN_ALWAYS_MODE_CHANGED进行处理,分别是

DefaultState(不作处理)

ApStaDisabledState(若开关打开,切换到StaDisabledWithScanState)

StaDisabledWithScanState(若开关关闭,切换到ApStaDisabledState)

 

2.4.1 wifi scan always打开

    class ApStaDisabledState extends State {
...
                case CMD_SCAN_ALWAYS_MODE_CHANGED:
                    if (mSettingsStore.isScanAlwaysAvailable()) {
                        transitionTo(mStaDisabledWithScanState);
                    }
                    break;

 

    class StaDisabledWithScanState extends State {
        private int mDeferredEnableSerialNumber = 0;
        private boolean mHaveDeferredEnable = false;
        private long mDisabledTimestamp;

        @Override
        public void enter() {
            // need to set the mode before starting supplicant because WSM will assume we are going
            // in to client mode
            mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_WITH_WIFI_OFF_MODE);
            mWifiStateMachine.setSupplicantRunning(true);
            // Supplicant can't restart right away, so not the time we switched off
            mDisabledTimestamp = SystemClock.elapsedRealtime();
            mDeferredEnableSerialNumber++;
            mHaveDeferredEnable = false;
            mWifiStateMachine.clearANQPCache();
        }

从如上代码及状态之间图示可以看出状态切换只走了StaDisabledWithScanState的enter方法

简单看了就是切换到了SCAN_ONLY_WITH_WIFI_OFF_MODE并且启动了supplicant

WSM

i 切换SCAN_ONLY_WITH_WIFI_OFF_MODE

    /**
     * TODO: doc
     */
    public void setOperationalMode(int mode) {
        if (mVerboseLoggingEnabled) log("setting operational mode to " + String.valueOf(mode));
        sendMessage(CMD_SET_OPERATIONAL_MODE, mode, 0);
    }

InitialState

                case CMD_SET_OPERATIONAL_MODE:
                    mOperationalMode = message.arg1;
                    if (mOperationalMode != DISABLED_MODE) {
                        sendMessage(CMD_START_SUPPLICANT);
                    }
                    break;

ii 启动supplicant

    /**
     * TODO: doc
     */
    public void setSupplicantRunning(boolean enable) {
        if (enable) {
            sendMessage(CMD_START_SUPPLICANT);
        } else {
            sendMessage(CMD_STOP_SUPPLICANT);
        }
    }

可以看到两步其实都下发了启动supplicant的命令,简单看起来有冗余,之后WiFi启动流程是差不多的,加载驱动-启动supplicant,从InitialState-SupplicantStartingState-SupplicantStartedState

    class SupplicantStartedState extends State {
        @Override
        public void enter() {
            if (mVerboseLoggingEnabled) {
                logd("SupplicantStartedState enter");
            }

            mWifiNative.setExternalSim(true);

            setRandomMacOui();
            mCountryCode.setReadyForChange(true);

            // We can't do this in the constructor because WifiStateMachine is created before the
            // wifi scanning service is initialized
            if (mWifiScanner == null) {
                mWifiScanner = mWifiInjector.getWifiScanner();

                synchronized (mWifiReqCountLock) {
                    mWifiConnectivityManager =
                            mWifiInjector.makeWifiConnectivityManager(mWifiInfo,
                                                                      hasConnectionRequests());
                    mWifiConnectivityManager.setUntrustedConnectionAllowed(mUntrustedReqCount > 0);
                    mWifiConnectivityManager.handleScreenStateChanged(mScreenOn);
                }
            }

            mWifiDiagnostics.startLogging(mVerboseLoggingEnabled);
            mIsRunning = true;
            updateBatteryWorkSource(null);
            /**
             * Enable bluetooth coexistence scan mode when bluetooth connection is active.
             * When this mode is on, some of the low-level scan parameters used by the
             * driver are changed to reduce interference with bluetooth
             */
            mWifiNative.setBluetoothCoexistenceScanMode(mBluetoothConnectionActive);
            // Check if there is a voice call on-going and set/reset the tx power limit
            // appropriately.
            if (mEnableVoiceCallSarTxPowerLimit) {
                if (getTelephonyManager().isOffhook()) {
                    sendMessage(CMD_SELECT_TX_POWER_SCENARIO,
                            WifiNative.TX_POWER_SCENARIO_VOICE_CALL);
                } else {
                    sendMessage(CMD_SELECT_TX_POWER_SCENARIO,
                            WifiNative.TX_POWER_SCENARIO_NORMAL);
                }
            }

            // initialize network state
            setNetworkDetailedState(DetailedState.DISCONNECTED);

            // Disable legacy multicast filtering, which on some chipsets defaults to enabled.
            // Legacy IPv6 multicast filtering blocks ICMPv6 router advertisements which breaks IPv6
            // provisioning. Legacy IPv4 multicast filtering may be re-enabled later via
            // IpClient.Callback.setFallbackMulticastFilter()
            mWifiNative.stopFilteringMulticastV4Packets();
            mWifiNative.stopFilteringMulticastV6Packets();

            if (mOperationalMode == SCAN_ONLY_MODE ||
                    mOperationalMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) {
                mWifiNative.disconnect();
                setWifiState(WIFI_STATE_DISABLED);
                transitionTo(mScanModeState);
            } else if (mOperationalMode == CONNECT_MODE) {
                setWifiState(WIFI_STATE_ENABLING);
                // Transitioning to Disconnected state will trigger a scan and subsequently AutoJoin
                transitionTo(mDisconnectedState);
            } else if (mOperationalMode == DISABLED_MODE) {
                transitionTo(mSupplicantStoppingState);
            }
。。。

之前梳理WiFi启动流程的时候OperationalMode都是CONNECT_MODE,之后会切换到DisconnectedState

而这次wifi scan always的OperationalMode是SCAN_ONLY_WITH_WIFI_OFF_MODE,这时会切换到ScanModeState

    class ScanModeState extends State {
        private int mLastOperationMode;
        @Override
        public void enter() {
            mLastOperationMode = mOperationalMode;
            mWifiStateTracker.updateState(WifiStateTracker.SCAN_MODE);
        }
        @Override
        public boolean processMessage(Message message) {
            logStateAndMessage(message, this);

            switch(message.what) {
                case CMD_SET_OPERATIONAL_MODE:
                    if (message.arg1 == CONNECT_MODE) {
                        mOperationalMode = CONNECT_MODE;
                        setWifiState(WIFI_STATE_ENABLING);
                        transitionTo(mDisconnectedState);
                    } else if (message.arg1 == DISABLED_MODE) {
                        transitionTo(mSupplicantStoppingState);
                    }
                    // Nothing to do
                    break;
                // Handle scan. All the connection related commands are
                // handled only in ConnectModeState
                case CMD_START_SCAN:
                    handleScanRequest(message);
                    break;
                default:
                    return NOT_HANDLED;
            }
            return HANDLED;
        }
    }

这个状态很简单,就处理两个消息,CMD_SET_OPERATIONAL_MODE和CMD_START_SCAN,主要看下CMD_START_SCAN的处理

(对比ConnectModeState可以处理CMD_START_CONNECT CMD_START_ROAM CMD_SAVE_CONFIG等等连接相关的命令)

    private void handleScanRequest(Message message) {
        ScanSettings settings = null;
        WorkSource workSource = null;

        // unbundle parameters
        Bundle bundle = (Bundle) message.obj;

        if (bundle != null) {
            settings = bundle.getParcelable(CUSTOMIZED_SCAN_SETTING);
            workSource = bundle.getParcelable(CUSTOMIZED_SCAN_WORKSOURCE);
        }

        Set freqs = null;
        if (settings != null && settings.channelSet != null) {
            freqs = new HashSet<>();
            for (WifiChannel channel : settings.channelSet) {
                freqs.add(channel.freqMHz);
            }
        }

        // Retrieve the list of hidden network SSIDs to scan for.
        List hiddenNetworks =
                mWifiConfigManager.retrieveHiddenNetworkList();

        // call wifi native to start the scan
        if (startScanNative(freqs, hiddenNetworks, workSource)) {
            // a full scan covers everything, clearing scan request buffer
            if (freqs == null)
                mBufferedScanMsg.clear();
            messageHandlingStatus = MESSAGE_HANDLING_STATUS_OK;
            return;
        }

        // if reach here, scan request is rejected

        if (!mIsScanOngoing) {
            // if rejection is NOT due to ongoing scan (e.g. bad scan parameters),

            // discard this request and pop up the next one
            if (mBufferedScanMsg.size() > 0) {
                sendMessage(mBufferedScanMsg.remove());
            }
            messageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
        } else if (!mIsFullScanOngoing) {
            // if rejection is due to an ongoing scan, and the ongoing one is NOT a full scan,
            // buffer the scan request to make sure specified channels will be scanned eventually
            if (freqs == null)
                mBufferedScanMsg.clear();
            if (mBufferedScanMsg.size() < SCAN_REQUEST_BUFFER_MAX_SIZE) {
                Message msg = obtainMessage(CMD_START_SCAN,
                        message.arg1, message.arg2, bundle);
                mBufferedScanMsg.add(msg);
            } else {
                // if too many requests in buffer, combine them into a single full scan
                bundle = new Bundle();
                bundle.putParcelable(CUSTOMIZED_SCAN_SETTING, null);
                bundle.putParcelable(CUSTOMIZED_SCAN_WORKSOURCE, workSource);
                Message msg = obtainMessage(CMD_START_SCAN, message.arg1, message.arg2, bundle);
                mBufferedScanMsg.clear();
                mBufferedScanMsg.add(msg);
            }
            messageHandlingStatus = MESSAGE_HANDLING_STATUS_LOOPED;
        } else {
            // mIsScanOngoing and mIsFullScanOngoing
            messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
        }
    }

这里还可以看到

        // Retrieve the list of hidden network SSIDs to scan for.
        List hiddenNetworks =
                mWifiConfigManager.retrieveHiddenNetworkList();

这边应该是可以让扫描可以扫描到隐藏网络的相关代码,梳理待续

可以理解为ScanModeState主要就处理扫描请求,和其所处模式SCAN_ONLY_WITH_WIFI_OFF_MODE的描述很相符,WiFi关闭情况下只进行扫描。

 

2.4.1 wifi scan always关闭

WifiController

    class ApStaDisabledState extends State {
        private int mDeferredEnableSerialNumber = 0;
        private boolean mHaveDeferredEnable = false;
        private long mDisabledTimestamp;

        @Override
        public void enter() {
            mWifiStateMachine.setSupplicantRunning(false);
            // Supplicant can't restart right away, so not the time we switched off
            mDisabledTimestamp = SystemClock.elapsedRealtime();
            mDeferredEnableSerialNumber++;
            mHaveDeferredEnable = false;
            mWifiStateMachine.clearANQPCache();
        }

这边看来就是将supplicant关闭即可

WSM ScanModeState的父状态为SupplicantStartedState,由于ScanModeState无法处理CMD_STOP_SUPPLICANT,由其父状态SupplicantStartedState处理。

    class SupplicantStartedState extends State {
...
        @Override
        public boolean processMessage(Message message) {
            logStateAndMessage(message, this);

            switch(message.what) {
                case CMD_STOP_SUPPLICANT:   /* Supplicant stopped by user */
                    if (mP2pSupported) {
                        transitionTo(mWaitForP2pDisableState);
                    } else {
                        transitionTo(mSupplicantStoppingState);
                    }
                    break;
...
        @Override
        public void exit() {
            mWifiDiagnostics.stopLogging();

            mIsRunning = false;
            updateBatteryWorkSource(null);
            mScanResults = new ArrayList<>();

            final Intent intent = new Intent(WifiManager.WIFI_SCAN_AVAILABLE);
            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
            intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WIFI_STATE_DISABLED);
            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
            mBufferedScanMsg.clear();

            mNetworkInfo.setIsAvailable(false);
            if (mNetworkAgent != null) mNetworkAgent.sendNetworkInfo(mNetworkInfo);
            mCountryCode.setReadyForChange(false);
        }
    }

切换到SupplicantStoppingState

    class SupplicantStoppingState extends State {
        @Override
        public void enter() {
            /* Send any reset commands to supplicant before shutting it down */
            handleNetworkDisconnect();

            String suppState = System.getProperty("init.svc.wpa_supplicant");
            if (suppState == null) suppState = "unknown";

            setWifiState(WIFI_STATE_DISABLING);
            mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);
            logd("SupplicantStoppingState: disableSupplicant "
                    + " init.svc.wpa_supplicant=" + suppState);
            if (mWifiNative.disableSupplicant()) {
                mWifiNative.closeSupplicantConnection();
                sendSupplicantConnectionChangedBroadcast(false);
                setWifiState(WIFI_STATE_DISABLED);
            } else {
                // Failed to disable supplicant
                handleSupplicantConnectionLoss(true);
            }
            transitionTo(mInitialState);
        }
    }

关闭supplicant后切换到InitialState状态

    class InitialState extends State {

        private void cleanup() {
            // Tearing down the client interfaces below is going to stop our supplicant.
            mWifiMonitor.stopAllMonitoring();

            mDeathRecipient.unlinkToDeath();
            mWifiNative.tearDown();
        }

        @Override
        public void enter() {
            mWifiStateTracker.updateState(WifiStateTracker.INVALID);
            cleanup();
        }

流程处理完毕。

 

3.总结

WIFI SCAN ALWAYS对应模式为SCAN_ONLY_WITH_WIFI_OFF_MODE,SCAN_ONLY_WITH_WIFI_OFF_MODE顾名思义WiFi关闭情况下只处理扫描请求。

你可能感兴趣的:(Wifi)