[Network]Android N新wifi connect&auto connectプロセス分析

37983 ワード

WifiConnectivityManager
前述した新しいAndroid N scanメカニズムは、今から新しいものを見てみましょう.WifiConnectivityManager、以前android connect wifiとscanの操作はwifistatemachineの中に置いてあり、全体が雑然としているように見えます.今googleはandroid Nの中で新しいものを作って、WifiConnectivityManager、これを通じてscanとconnectを管理します.ここではwifi connectのssidの流れからこのWifiConnectivityManagerを見てみます.
WifiManager.connect()
このAPIは変動していません.
    public void connect(WifiConfiguration config, ActionListener listener) {
        if (config == null) throw new IllegalArgumentException("config cannot be null");
        // Use INVALID_NETWORK_ID for arg1 when passing a config object
        // arg1 is used to pass network id when the network already exists
        getChannel().sendMessage(CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID,
                putListener(listener), config);
    }

WifiserviceImpl.JAvaではこのCMDを受け取り、対応する処理をします
/*
WifiServiceImpl.java (frameworks\opt
et\wifi\service\java\com\android\server\wifi) */
*/ private class ClientHandler extends Handler { ClientHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { ............ case WifiManager.CONNECT_NETWORK: case WifiManager.SAVE_NETWORK: { WifiConfiguration config = (WifiConfiguration) msg.obj; int networkId = msg.arg1; if (msg.what == WifiManager.SAVE_NETWORK) { Slog.d("WiFiServiceImpl ", "SAVE" + " nid=" + Integer.toString(networkId) + " uid=" + msg.sendingUid + " name=" + mContext.getPackageManager().getNameForUid(msg.sendingUid)); } if (msg.what == WifiManager.CONNECT_NETWORK) { Slog.d("WiFiServiceImpl ", "CONNECT " + " nid=" + Integer.toString(networkId) + " uid=" + msg.sendingUid + " name=" + mContext.getPackageManager().getNameForUid(msg.sendingUid)); } if (config != null && isValid(config)) { if (DBG) Slog.d(TAG, "Connect with config" + config); // WifiStatemachine CMD mWifiStateMachine.sendMessage(Message.obtain(msg)); } else if (config == null && networkId != WifiConfiguration.INVALID_NETWORK_ID) { if (DBG) Slog.d(TAG, "Connect with networkId" + networkId); mWifiStateMachine.sendMessage(Message.obtain(msg)); } else { Slog.e(TAG, "ClientHandler.handleMessage ignoring invalid msg=" + msg); if (msg.what == WifiManager.CONNECT_NETWORK) { replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED, WifiManager.INVALID_ARGS); } else { replyFailed(msg, WifiManager.SAVE_NETWORK_FAILED, WifiManager.INVALID_ARGS); } } break; } .........................

wifistatemachinemachineを見ると、ConnectModeStateでこれを処理するのが一般的です.
/*
WifiStateMachine.java (frameworks\opt
et\wifi\service\java\com\android\server\wifi) */
class ConnectModeState extends State { .......... case WifiManager.CONNECT_NETWORK: ............................. /* Tell network selection the user did try to connect to that network if from settings */ boolean persist = mWifiConfigManager.checkConfigOverridePermission(message.sendingUid); // mLastSelectedConfiguration , mWifiConfigManager.setAndEnableLastSelectedConfiguration(netId); if (mWifiConnectivityManager != null) { // WifiConnectivityManager mWifiConnectivityManager.connectToUserSelectNetwork(netId, persist); } //Start a new ConnectionEvent due to connect_network, this is always user //selected mWifiMetrics.startConnectionEvent(config, mTargetRoamBSSID, WifiMetricsProto.ConnectionEvent.ROAM_USER_SELECTED); if (mWifiConfigManager.selectNetwork(config, /* updatePriorities = */ true, message.sendingUid) && mWifiNative.reconnect()) { lastConnectAttemptTimestamp = System.currentTimeMillis(); targetWificonfiguration = mWifiConfigManager.getWifiConfiguration(netId); /* The state tracker handles enabling networks upon completion/failure */ mSupplicantStateTracker.sendMessage(WifiManager.CONNECT_NETWORK); replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED); if (didDisconnect) { /* Expect a disconnection from the old connection */ transitionTo(mDisconnectingState); } else if (updatedExisting && getCurrentState() == mConnectedState && getCurrentWifiConfiguration().networkId == netId) { // Update the current set of network capabilities, but stay in the // current state. updateCapabilities(config); } else { /** * Directly go to disconnected state where we * process the connection events from supplicant */ transitionTo(mDisconnectedState); } } else { loge("Failed to connect config: " + config + " netId: " + netId); replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED, WifiManager.ERROR); reportConnectionAttemptEnd( WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED, WifiMetricsProto.ConnectionEvent.HLF_NONE); break; }

ユーザー指定のSSIDを接続する
/*
WifiConnectivityManager.java (frameworks\opt
et\wifi\service\java\com\android\server\wifi) */
/** * Handler when user specifies a particular network to connect to */ public void connectToUserSelectNetwork(int netId, boolean persistent) { Log.i(TAG, "connectToUserSelectNetwork: netId=" + netId + " persist=" + persistent); //QualifiedNetworkSelector ssid ① mQualifiedNetworkSelector.userSelectNetwork(netId, persistent); clearConnectionAttemptTimeStamps(); }

WifiQualifiedNetworkSelector
実はこのclassの設計は、信号を自動的に接続するのに良いwifi ssidです.ユーザーをホットスポットに接続する論理をここに統合するのは、android 5.1でよく現れるユーザーがホットスポットに接続する操作をトリガーすることを避けるためであり、結果としてバックグラウンドauto connect自身がホットスポットを選択しても接続し、結果として2つのflowがけんかをする
/**
 * This class looks at all the connectivity scan results then
 * select an network for the phone to connect/roam to.
 */

①箇所のuserSelectNetworkを見てください
    /**
     * This API is called when user explicitly select a network. Currently, it is used in following
     * cases:
     * (1) User explicitly choose to connect to a saved network
     * (2) User save a network after add a new network
     * (3) User save a network after modify a saved network
     * Following actions will be triggered:
     * 1. if this network is disabled, we need re-enable it again
     * 2. we considered user prefer this network over all the networks visible in latest network
     *    selection procedure
     *
     * @param netId new network ID for either the network the user choose or add
     * @param persist whether user has the authority to overwrite current connect choice
     * @return true -- There is change made to connection choice of any saved network
     *         false -- There is no change made to connection choice of any saved network
     */
    public boolean userSelectNetwork(int netId, boolean persist) {
        WifiConfiguration selected = mWifiConfigManager.getWifiConfiguration(netId);
        localLog("userSelectNetwork:" + netId + " persist:" + persist);
        if (selected == null || selected.SSID == null) {
            localLoge("userSelectNetwork: Bad configuration with nid=" + netId);
            return false;
        }

        //         wifi   enabled
        if (!selected.getNetworkSelectionStatus().isNetworkEnabled()) {
            mWifiConfigManager.updateNetworkSelectionStatus(netId,
                    WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
        }

        if (!persist) {
            localLog("User has no privilege to overwrite the current priority");
            return false;
        }

        boolean change = false;
        String key = selected.configKey();
        // This is only used for setting the connect choice timestamp for debugging purposes.
        long currentTime = mClock.currentTimeMillis();
        //    sacn result    ssid,        selected 
        List savedNetworks = mWifiConfigManager.getSavedNetworks();

        for (WifiConfiguration network : savedNetworks) {
            WifiConfiguration config = mWifiConfigManager.getWifiConfiguration(network.networkId);
            WifiConfiguration.NetworkSelectionStatus status = config.getNetworkSelectionStatus();
            if (config.networkId == selected.networkId) {
                if (status.getConnectChoice() != null) {
                    //        selected ssid remove 
                    localLog("Remove user selection preference of " + status.getConnectChoice()
                            + " Set Time: " + status.getConnectChoiceTimestamp() + " from "
                            + config.SSID + " : " + config.networkId);
                    status.setConnectChoice(null);
                    status.setConnectChoiceTimestamp(WifiConfiguration.NetworkSelectionStatus
                            .INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
                    change = true;
                }
                continue;
            }
            //          
            if (status.getSeenInLastQualifiedNetworkSelection()
                    && (status.getConnectChoice() == null
                    || !status.getConnectChoice().equals(key))) {
                localLog("Add key:" + key + " Set Time: " + currentTime + " to "
                        + getNetworkString(config));
                status.setConnectChoice(key);
                status.setConnectChoiceTimestamp(currentTime);
                change = true;
            }
        }
        //Write this change to file
        //  wificonfig 
        if (change) {
            mWifiConfigManager.writeKnownNetworkHistory();
            return true;
        }

        return false;
    }

ここまで来たら終わり...この部分の論理はattempt auto joinが走るのを防ぐための論理であるべきだ.
前のWiFiConnectivityManagerに戻り、①を走り終えたら、ついてくる②
    public void connectToUserSelectNetwork(int netId, boolean persistent) {
        Log.i(TAG, "connectToUserSelectNetwork: netId=" + netId
                   + " persist=" + persistent);

        ①mQualifiedNetworkSelector.userSelectNetwork(netId, persistent);

        ②clearConnectionAttemptTimeStamps();
    }

②のコードは簡単で、接続しようとするたびに存在するタイムスタンプを管理するConnectionAttemptTimeStampsが呼び出され、ConnectionAttemptTimeStampsは双方向のリストです.
    /**
     * This is used to clear the connection attempt rate limiter. This is done when the user
     * explicitly tries to connect to a specified network.
     */
    private void clearConnectionAttemptTimeStamps() {
        mConnectionAttemptTimeStamps.clear();
    }

前のWifiStatemachineに戻り、後ろについて直接call mWifiNative.reconnect()はSSIDを接続します.
                    if (mWifiConfigManager.selectNetwork(config, /* updatePriorities = */ true,
                            message.sendingUid) && mWifiNative.reconnect()) {
                        lastConnectAttemptTimestamp = System.currentTimeMillis();
                        targetWificonfiguration = mWifiConfigManager.getWifiConfiguration(netId);

                        /* The state tracker handles enabling networks upon completion/failure */
                        mSupplicantStateTracker.sendMessage(WifiManager.CONNECT_NETWORK);
                        replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
                        if (didDisconnect) {
                            /* Expect a disconnection from the old connection */
                            transitionTo(mDisconnectingState);
                        } 

Auto connect
Android Nはこのauto connectの部分を大きく変更し、やり直しました.このAutoconnectのメカニズムは、wifiホットスポットのローミング用に作られています.つまり、信号などの品質の良いホットスポットが見つかったときに切ります.しかし、以前は、特にandroid Lという部分がユーザーconnectのflowと衝突し、多くのバグが発生し、鶏の肋骨だった.
前述した新しいAndroid N scanメカニズムは、いくつかのscanシーンのアプリケーションについて言及しており、このauto connectは使用されます.ここでは1つのcaseだけを分析すればいいです.
startPeriodicScan、これはスクリーンが明るいとき、バックグラウンドでscanの操作をしています.
/*
WifiConnectivityManager.java (frameworks\opt
et\wifi\service\java\com\android\server\wifi) */
// Start a periodic scan when screen is on private void startPeriodicScan(boolean scanImmediately) { mPnoScanListener.resetLowRssiNetworkRetryDelay(); // Due to b/28020168, timer based single scan will be scheduled // to provide periodic scan in an exponential backoff fashion. if (!ENABLE_BACKGROUND_SCAN) { if (scanImmediately) { resetLastPeriodicSingleScanTimeStamp(); } mPeriodicSingleScanInterval = PERIODIC_SCAN_INTERVAL_MS; startPeriodicSingleScan(); } else { ScanSettings settings = new ScanSettings(); settings.band = getScanBand(); settings.reportEvents = WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; settings.numBssidsPerScan = 0; settings.periodInMs = PERIODIC_SCAN_INTERVAL_MS; mPeriodicScanListener.clearScanDetails(); // mScanner.startBackgroundScan scan,worksource WIFI_WORK_SOURCE mScanner.startBackgroundScan(settings, mPeriodicScanListener, WIFI_WORK_SOURCE); } }

ここに入ってきたリスナーを見てください:mPeriodicScanListener
/*
WifiConnectivityManager.java (frameworks\opt
et\wifi\service\java\com\android\server\wifi) */
// Periodic scan results listener. A periodic scan is initiated when // screen is on. private class PeriodicScanListener implements WifiScanner.ScanListener { private List mScanDetails = new ArrayList(); public void clearScanDetails() { mScanDetails.clear(); } @Override public void onSuccess() { localLog("PeriodicScanListener onSuccess"); // reset the count mScanRestartCount = 0; } @Override public void onFailure(int reason, String description) { Log.e(TAG, "PeriodicScanListener onFailure:" + " reason: " + reason + " description: " + description); // reschedule the scan if (mScanRestartCount++ < MAX_SCAN_RESTART_ALLOWED) { scheduleDelayedConnectivityScan(RESTART_SCAN_DELAY_MS); } else { mScanRestartCount = 0; Log.e(TAG, "Failed to successfully start periodic scan for " + MAX_SCAN_RESTART_ALLOWED + " times"); } } @Override public void onPeriodChanged(int periodInMs) { localLog("PeriodicScanListener onPeriodChanged: " + "actual scan period " + periodInMs + "ms"); } @Override public void onResults(WifiScanner.ScanData[] results) { // scan , handleScanResults handleScanResults(mScanDetails, "PeriodicScanListener"); clearScanDetails(); } @Override public void onFullResult(ScanResult fullScanResult) { if (mDbg) { localLog("PeriodicScanListener onFullResult: " + fullScanResult.SSID + " capabilities " + fullScanResult.capabilities); } mScanDetails.add(ScanDetailUtil.toScanDetail(fullScanResult)); } }

この関数を見て何をしましたか?

    /**
     * Handles 'onResult' callbacks for the Periodic, Single & Pno ScanListener.
     * Executes selection of potential network candidates, initiation of connection attempt to that
     * network.
     *
     * @return true - if a candidate is selected by QNS
     *         false - if no candidate is selected by QNS
     */
    private boolean handleScanResults(List scanDetails, String listenerName) {
        localLog(listenerName + " onResults: start QNS");
        //  QualifiedNetworkSelector     ssid,        。       
        ①WifiConfiguration candidate =
                mQualifiedNetworkSelector.selectQualifiedNetwork(false,
                mUntrustedConnectionAllowed, scanDetails,
                mStateMachine.isLinkDebouncing(), mStateMachine.isConnected(),
                mStateMachine.isDisconnected(),
                mStateMachine.isSupplicantTransientState());
        mWifiLastResortWatchdog.updateAvailableNetworks(
                mQualifiedNetworkSelector.getFilteredScanDetails());
        if (candidate != null) {
            localLog(listenerName + ": QNS candidate-" + candidate.SSID);
            ②connectToNetwork(candidate);
            return true;
        } else {
            return false;
        }
    }

① selectQualifiedNetwork
    /**
     * ToDo: This should be called in Connectivity Manager when it gets new scan result
     * check whether a network slection is needed. If need, check all the new scan results and
     * select a new qualified network/BSSID to connect to
     *
     * @param forceSelectNetwork true -- start a qualified network selection anyway,no matter
     *                           current network is already qualified or not.
     *                           false -- if current network is already qualified, do not do new
     *                           selection
     * @param isUntrustedConnectionsAllowed true -- user allow to connect to untrusted network
     *                                      false -- user do not allow to connect to untrusted
     *                                      network
     * @param scanDetails latest scan result obtained (should be connectivity scan only)
     * @param isLinkDebouncing true -- Link layer is under debouncing
     *                         false -- Link layer is not under debouncing
     * @param isConnected true -- device is connected to an AP currently
     *                    false -- device is not connected to an AP currently
     * @param isDisconnected true -- WifiStateMachine is at disconnected state
     *                       false -- WifiStateMachine is not at disconnected state
     * @param isSupplicantTransient true -- supplicant is in a transient state
     *                              false -- supplicant is not in a transient state
     * @return the qualified network candidate found. If no available candidate, return null
     */
    public WifiConfiguration selectQualifiedNetwork(boolean forceSelectNetwork ,
            boolean isUntrustedConnectionsAllowed, List  scanDetails,
            boolean isLinkDebouncing, boolean isConnected, boolean isDisconnected,
            boolean isSupplicantTransient) {

② connectToNetwork(candidate); アルゴリズムで計算したターゲットssidに接続を開始し、candidateはWifiConfigurationオブジェクトです.
    /**
     * Attempt to connect to a network candidate.
     *
     * Based on the currently connected network, this menthod determines whether we should
     * connect or roam to the network candidate recommended by QNS.
     */
    private void connectToNetwork(WifiConfiguration candidate) {
        ScanResult scanResultCandidate = candidate.getNetworkSelectionStatus().getCandidate();
        if (scanResultCandidate == null) {
            Log.e(TAG, "connectToNetwork: bad candidate - "  + candidate
                    + " scanResult: " + scanResultCandidate);
            return;
        }

        String targetBssid = scanResultCandidate.BSSID;
        String targetAssociationId = candidate.SSID + " : " + targetBssid;

        //         ssid      attempt   ssid,   supplicant         ssid,   
        // Check if we are already connected or in the process of connecting to the target
        // BSSID. mWifiInfo.mBSSID tracks the currently connected BSSID. This is checked just
        // in case the firmware automatically roamed to a BSSID different from what QNS
        // selected.
        if (targetBssid != null
                && (targetBssid.equals(mLastConnectionAttemptBssid)
                    || targetBssid.equals(mWifiInfo.getBSSID()))
                && SupplicantState.isConnecting(mWifiInfo.getSupplicantState())) {
            localLog("connectToNetwork: Either already connected "
                    + "or is connecting to " + targetAssociationId);
            return;
        }

        Long elapsedTimeMillis = mClock.elapsedRealtime();  
            /**
     * This checks the connection attempt rate and recommends whether the connection attempt
     * should be skipped or not. This attempts to rate limit the rate of connections to
     * prevent us from flapping between networks and draining battery rapidly.
     */    
         //  attempt    ,        ,      
        if (!mScreenOn && shouldSkipConnectionAttempt(elapsedTimeMillis)) {
            localLog("connectToNetwork: Too many connection attempts. Skipping this attempt!");
            mTotalConnectivityAttemptsRateLimited++;
            return;
        }
        //        ,       noteConnectionAttempt
        noteConnectionAttempt(elapsedTimeMillis);

        mLastConnectionAttemptBssid = targetBssid;

        WifiConfiguration currentConnectedNetwork = mConfigManager
                .getWifiConfiguration(mWifiInfo.getNetworkId());
        String currentAssociationId = (currentConnectedNetwork == null) ? "Disconnected" :
                (mWifiInfo.getSSID() + " : " + mWifiInfo.getBSSID());

        //       autoconnect   
        if (currentConnectedNetwork != null
                && (currentConnectedNetwork.networkId == candidate.networkId
                || currentConnectedNetwork.isLinked(candidate))) {
            localLog("connectToNetwork: Roaming from " + currentAssociationId + " to "
                        + targetAssociationId);
            mStateMachine.autoRoamToNetwork(candidate.networkId, scanResultCandidate);
        } else {
            localLog("connectToNetwork: Reconnect from " + currentAssociationId + " to "
                        + targetAssociationId);
            mStateMachine.autoConnectToNetwork(candidate.networkId, scanResultCandidate.BSSID);
        }
    }

次はWifistatemachineに任せて処理します.

    /**
     * Automatically connect to the network specified
     *
     * @param networkId ID of the network to connect to
     * @param bssid BSSID of the network
     */
    public void autoConnectToNetwork(int networkId, String bssid) {
        sendMessage(CMD_AUTO_CONNECT, networkId, 0, bssid);
    }

    /**
     * Automatically roam to the network specified
     *
     * @param networkId ID of the network to roam to
     * @param scanResult scan result which identifies the network to roam to
     */
    public void autoRoamToNetwork(int networkId, ScanResult scanResult) {
        sendMessage(CMD_AUTO_ROAM, networkId, 0, scanResult);
    }