(一百六十二)Android P PasspointNetworkEvaluator

前言:之前在(一百三十一)Android O WiFi自动连接评分机制学习 学习了SavedNetworkEvaluator的筛选,现在看下PasspointNetworkEvaluator

目录

1. PasspointNetworkEvaluator

1.1 PasspointManager.sweepCache

1.2 NetworkDetail.isInterworking

1.3 PasspointManager.matchProvider

1.4 findBestNetwork

2.总结


1. PasspointNetworkEvaluator

    @Override
    public WifiConfiguration evaluateNetworks(List scanDetails,
                    WifiConfiguration currentNetwork, String currentBssid,
                    boolean connected, boolean untrustedNetworkAllowed,
                    List> connectableNetworks) {
        // Sweep the ANQP cache to remove any expired ANQP entries.
        mPasspointManager.sweepCache();

        // Go through each ScanDetail and find the best provider for each ScanDetail.
        List candidateList = new ArrayList<>();
        for (ScanDetail scanDetail : scanDetails) {
            // Skip non-Passpoint APs.
            if (!scanDetail.getNetworkDetail().isInterworking()) {
                continue;
            }

            // Find the best provider for this ScanDetail.
            Pair bestProvider =
                    mPasspointManager.matchProvider(scanDetail.getScanResult());
            if (bestProvider != null) {
                if (bestProvider.first.isSimCredential() && !mWifiConfigManager.isSimPresent()) {
                    // Skip providers backed by SIM credential when SIM is not present.
                    continue;
                }
                candidateList.add(new PasspointNetworkCandidate(
                        bestProvider.first, bestProvider.second, scanDetail));
            }
        }

        // Done if no candidate is found.
        if (candidateList.isEmpty()) {
            localLog("No suitable Passpoint network found");
            return null;
        }

        // Find the best Passpoint network among all candidates.
        PasspointNetworkCandidate bestNetwork =
                findBestNetwork(candidateList, currentNetwork == null ? null : currentNetwork.SSID);

        // Return the configuration for the current connected network if it is the best network.
        if (currentNetwork != null && TextUtils.equals(currentNetwork.SSID,
                ScanResultUtil.createQuotedSSID(bestNetwork.mScanDetail.getSSID()))) {
            localLog("Staying with current Passpoint network " + currentNetwork.SSID);

            // Update current network with the latest scan info.
            mWifiConfigManager.setNetworkCandidateScanResult(currentNetwork.networkId,
                    bestNetwork.mScanDetail.getScanResult(), 0);
            mWifiConfigManager.updateScanDetailForNetwork(currentNetwork.networkId,
                    bestNetwork.mScanDetail);

            connectableNetworks.add(Pair.create(bestNetwork.mScanDetail, currentNetwork));
            return currentNetwork;
        }

        WifiConfiguration config = createWifiConfigForProvider(bestNetwork);
        if (config != null) {
            connectableNetworks.add(Pair.create(bestNetwork.mScanDetail, config));
            localLog("Passpoint network to connect to: " + config.SSID);
        }
        return config;
    }

1.1 PasspointManager.sweepCache

Sweep the ANQP cache to remove expired entries

The Access Network Query Protocol (ANQP

PasspointManager.java
    /**
     * Sweep the ANQP cache to remove expired entries.
     */
    public void sweepCache() {
        mAnqpCache.sweep();
    }

AnqpCache.java

    /**
     * Go through the cache to remove any expired entries.
     */
    public void sweep() {
        long now = mClock.getElapsedSinceBootMillis();
        // Check if it is time to perform the sweep.
        if (now < mLastSweep + CACHE_SWEEP_INTERVAL_MILLISECONDS) {
            return;
        }

        // Get all expired keys.
        List expiredKeys = new ArrayList<>();
        for (Map.Entry entry : mANQPCache.entrySet()) {
            if (entry.getValue().expired(now)) {
                expiredKeys.add(entry.getKey());
            }
        }

        // Remove all expired entries.
        for (ANQPNetworkKey key : expiredKeys) {
            mANQPCache.remove(key);
        }
        mLastSweep = now;
    }

 

1.2 NetworkDetail.isInterworking

NetworkDetail.java

    public boolean isInterworking() {
        return mAnt != null;
    }

构造函数
    public NetworkDetail(String bssid, ScanResult.InformationElement[] infoElements,
            List anqpLines, int freq) {
...
        InformationElementUtil.Interworking interworking =
                new InformationElementUtil.Interworking();
...

        try {
            for (ScanResult.InformationElement ie : infoElements) {
                iesFound.add(ie.id);
                switch (ie.id) {
                    case ScanResult.InformationElement.EID_SSID:
                        ssidOctets = ie.bytes;
                        break;
                    case ScanResult.InformationElement.EID_BSS_LOAD:
                        bssLoad.from(ie);
                        break;
                    case ScanResult.InformationElement.EID_HT_OPERATION:
                        htOperation.from(ie);
                        break;
                    case ScanResult.InformationElement.EID_VHT_OPERATION:
                        vhtOperation.from(ie);
                        break;
                    case ScanResult.InformationElement.EID_INTERWORKING:
                        interworking.from(ie);
                        break;
...
        mAnt = interworking.ant;

再看下Interworking的from

InformationElementUtil.java
    public static class Interworking {
        public NetworkDetail.Ant ant = null;
        public boolean internet = false;
        public long hessid = 0L;

        public void from(InformationElement ie) {
            if (ie.id != InformationElement.EID_INTERWORKING) {
                throw new IllegalArgumentException("Element id is not INTERWORKING, : " + ie.id);
            }
            ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
            int anOptions = data.get() & Constants.BYTE_MASK;
            ant = NetworkDetail.Ant.values()[anOptions & 0x0f];
            internet = (anOptions & 0x10) != 0;
            // There are only three possible lengths for the Interworking IE:
            // Len 1: Access Network Options only
            // Len 3: Access Network Options & Venue Info
            // Len 7: Access Network Options & HESSID
            // Len 9: Access Network Options, Venue Info, & HESSID
            if (ie.bytes.length != 1
                    && ie.bytes.length != 3
                    && ie.bytes.length != 7
                    && ie.bytes.length != 9) {
                throw new IllegalArgumentException(
                        "Bad Interworking element length: " + ie.bytes.length);
            }

            if (ie.bytes.length == 3 || ie.bytes.length == 9) {
                int venueInfo = (int) ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 2);
            }

            if (ie.bytes.length == 7 || ie.bytes.length == 9) {
                hessid = ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 6);
            }
        }
    }

NetworkDetail是否支持passpoint的信息是从ie中解析出来的

 

1.3 PasspointManager.matchProvider

    /**
     * Find the best provider that can provide service through the given AP, which means the
     * provider contained credential to authenticate with the given AP.
     *
     * Here is the current precedence of the matching rule in descending order:
     * 1. Home Provider
     * 2. Roaming Provider
     *
     * A {code null} will be returned if no matching is found.
     *
     * @param scanResult The scan result associated with the AP
     * @return A pair of {@link PasspointProvider} and match status.
     */
    public Pair matchProvider(ScanResult scanResult) {
        List> allMatches = getAllMatchedProviders(
                scanResult);
        if (allMatches == null) {
            return null;
        }

        Pair bestMatch = null;
        for (Pair match : allMatches) {
            if (match.second == PasspointMatch.HomeProvider) {
                bestMatch = match;
                break;
            }
            if (match.second == PasspointMatch.RoamingProvider && bestMatch == null) {
                bestMatch = match;
            }
        }

        if (bestMatch != null) {
            Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID,
                    bestMatch.first.getConfig().getHomeSp().getFqdn(),
                    bestMatch.second == PasspointMatch.HomeProvider ? "Home Provider"
                            : "Roaming Provider"));
        } else {
            Log.d(TAG, "Match not found for " + scanResult.SSID);
        }
        return bestMatch;
    }

看下getAllMatchedProviders

    /**
     * Return a list of all providers that can provide service through the given AP.
     *
     * @param scanResult The scan result associated with the AP
     * @return a list of pairs of {@link PasspointProvider} and match status.
     */
    public List> getAllMatchedProviders(
            ScanResult scanResult) {
        List> allMatches = new ArrayList<>();

        // Retrieve the relevant information elements, mainly Roaming Consortium IE and Hotspot 2.0
        // Vendor Specific IE.
        InformationElementUtil.RoamingConsortium roamingConsortium =
                InformationElementUtil.getRoamingConsortiumIE(scanResult.informationElements);
        InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE(
                scanResult.informationElements);

        // Lookup ANQP data in the cache.
        long bssid;
        try {
            bssid = Utils.parseMac(scanResult.BSSID);
        } catch (IllegalArgumentException e) {
            Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
            return allMatches;
        }
        ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid,
                vsa.anqpDomainID);
        ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey);

        if (anqpEntry == null) {
            mAnqpRequestManager.requestANQPElements(bssid, anqpKey,
                    roamingConsortium.anqpOICount > 0,
                    vsa.hsRelease  == NetworkDetail.HSRelease.R2);
            Log.d(TAG, "ANQP entry not found for: " + anqpKey);
            return allMatches;
        }

        for (Map.Entry entry : mProviders.entrySet()) {
            PasspointProvider provider = entry.getValue();
            PasspointMatch matchStatus = provider.match(anqpEntry.getElements(), roamingConsortium);
            if (matchStatus == PasspointMatch.HomeProvider
                    || matchStatus == PasspointMatch.RoamingProvider) {
                allMatches.add(Pair.create(provider, matchStatus));
            }
        }

        if (allMatches.size() != 0) {
            for (Pair match : allMatches) {
                Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID,
                        match.first.getConfig().getHomeSp().getFqdn(),
                        match.second == PasspointMatch.HomeProvider ? "Home Provider"
                                : "Roaming Provider"));
            }
        } else {
            Log.d(TAG, "No matches not found for " + scanResult.SSID);
        }

        return allMatches;
    }

简单看起来mAnqpCache要缓存对应扫描结果的anqpKey才行,类似于普通WiFi的已保存网络么。。。

对应的add逻辑是在这边

    private class CallbackHandler implements PasspointEventHandler.Callbacks {
        private final Context mContext;
        CallbackHandler(Context context) {
            mContext = context;
        }

        @Override
        public void onANQPResponse(long bssid,
                Map anqpElements) {
            // Notify request manager for the completion of a request.
            ANQPNetworkKey anqpKey =
                    mAnqpRequestManager.onRequestCompleted(bssid, anqpElements != null);
            if (anqpElements == null || anqpKey == null) {
                // Query failed or the request wasn't originated from us (not tracked by the
                // request manager). Nothing to be done.
                return;
            }

            // Add new entry to the cache.
            mAnqpCache.addEntry(anqpKey, anqpElements);
        }

http://androidxref.com/9.0.0_r3/xref/frameworks/opt/net/wifi/service/java/com/android/server/wifi/hotspot2/PasspointEventHandler.java#114

    /**
     * Invoked when ANQP query is completed.
     * TODO(zqiu): currently ANQP completion notification is through WifiMonitor,
     * this shouldn't be needed once we switch over to wificond for ANQP requests.
     * @param anqpEvent ANQP result data retrieved. ANQP elements could be empty in the event to
     *                  indicate any failures.
     */
    public void notifyANQPDone(AnqpEvent anqpEvent) {
        if (anqpEvent == null) return;
        mCallbacks.onANQPResponse(anqpEvent.getBssid(), anqpEvent.getElements());
    }

http://androidxref.com/9.0.0_r3/xref/frameworks/opt/net/wifi/service/java/com/android/server/wifi/hotspot2/PasspointManager.java#468

    /**
     * Notify the completion of an ANQP request.
     * TODO(zqiu): currently the notification is done through WifiMonitor,
     * will no longer be the case once we switch over to use wificond.
     */
    public void notifyANQPDone(AnqpEvent anqpEvent) {
        mHandler.notifyANQPDone(anqpEvent);
    }

http://androidxref.com/9.0.0_r3/xref/frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java#4437

                case WifiMonitor.ANQP_DONE_EVENT:
                    // TODO(zqiu): remove this when switch over to wificond for ANQP requests.
                    mPasspointManager.notifyANQPDone((AnqpEvent) message.obj);
                    break;

...
        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.ANQP_DONE_EVENT, getHandler());
ANQP_DONE_EVENT还是WifiMonitor报上来的

WifiMonitor

    /**
     * Broadcast the ANQP done event to all the handlers registered for this event.
     *
     * @param iface Name of iface on which this occurred.
     * @param anqpEvent ANQP result retrieved.
     */
    public void broadcastAnqpDoneEvent(String iface, AnqpEvent anqpEvent) {
        sendMessage(iface, ANQP_DONE_EVENT, anqpEvent);
    }

http://androidxref.com/9.0.0_r3/xref/frameworks/opt/net/wifi/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java#2383

    private class SupplicantStaIfaceHalCallback extends ISupplicantStaIfaceCallback.Stub {
        private String mIfaceName;
        private boolean mStateIsFourway = false; // Used to help check for PSK password mismatch

        SupplicantStaIfaceHalCallback(@NonNull String ifaceName) {
            mIfaceName = ifaceName;
        }
...
        @Override
        public void onStateChanged(int newState, byte[/* 6 */] bssid, int id,
                                   ArrayList ssid) {
            synchronized (mLock) {
                logCallback("onStateChanged");
                SupplicantState newSupplicantState = supplicantHidlStateToFrameworkState(newState);
                WifiSsid wifiSsid =
                        WifiSsid.createFromByteArray(NativeUtil.byteArrayFromArrayList(ssid));
                String bssidStr = NativeUtil.macAddressFromByteArray(bssid);
                mStateIsFourway = (newState == ISupplicantStaIfaceCallback.State.FOURWAY_HANDSHAKE);
                if (newSupplicantState == SupplicantState.COMPLETED) {
                    mWifiMonitor.broadcastNetworkConnectionEvent(
                            mIfaceName, getCurrentNetworkId(mIfaceName), bssidStr);
                }
                mWifiMonitor.broadcastSupplicantStateChangeEvent(
                        mIfaceName, getCurrentNetworkId(mIfaceName), wifiSsid,
                        bssidStr, newSupplicantState);
            }
        }

这个后面再看吧。。。

 

1.4 findBestNetwork

    /**
     * Given a list of Passpoint networks (with both provider and scan info), find and return
     * the one with highest score.  The score is calculated using
     * {@link PasspointNetworkScore#calculateScore}.
     *
     * @param networkList List of Passpoint networks
     * @param currentNetworkSsid The SSID of the currently connected network, null if not connected
     * @return {@link PasspointNetworkCandidate}
     */
    private PasspointNetworkCandidate findBestNetwork(
            List networkList, String currentNetworkSsid) {
        PasspointNetworkCandidate bestCandidate = null;
        int bestScore = Integer.MIN_VALUE;
        for (PasspointNetworkCandidate candidate : networkList) {
            ScanDetail scanDetail = candidate.mScanDetail;
            PasspointMatch match = candidate.mMatchStatus;

            boolean isActiveNetwork = TextUtils.equals(currentNetworkSsid,
                    ScanResultUtil.createQuotedSSID(scanDetail.getSSID()));
            int score = PasspointNetworkScore.calculateScore(match == PasspointMatch.HomeProvider,
                    scanDetail, mPasspointManager.getANQPElements(scanDetail.getScanResult()),
                    isActiveNetwork);

            if (score > bestScore) {
                bestCandidate = candidate;
                bestScore = score;
            }
        }
        localLog("Best Passpoint network " + bestCandidate.mScanDetail.getSSID() + " provided by "
                + bestCandidate.mProvider.getConfig().getHomeSp().getFqdn());
        return bestCandidate;
    }

PasspointNetworkScore

    /**
     * Calculate and return a score associated with the given Passpoint network.
     * The score is calculated with the following preferences:
     * - Prefer home provider
     * - Prefer network that provides Internet access
     * - Prefer network with active WAN port with available load
     * - Prefer network that provides unrestricted IP address
     * - Prefer currently active network
     * - Prefer AP with higher RSSI
     *
     * This can be expanded for additional preference in the future (e.g. AP station count, link
     * speed, and etc).
     *
     * @param isHomeProvider Flag indicating home provider
     * @param scanDetail The ScanDetail associated with the AP
     * @param isActiveNetwork Flag indicating current active network
     * @return integer score
     */
    public static int calculateScore(boolean isHomeProvider, ScanDetail scanDetail,
            Map anqpElements, boolean isActiveNetwork) {
        NetworkDetail networkDetail = scanDetail.getNetworkDetail();
        int score = 0;
        if (isHomeProvider) {
            score += HOME_PROVIDER_AWARD;
        }

        // Adjust score based on Internet accessibility.
        score += (networkDetail.isInternet() ? 1 : -1) * INTERNET_ACCESS_AWARD;

        // Adjust score based on the network type.
        score += NETWORK_TYPE_SCORES.get(networkDetail.getAnt());

        if (anqpElements != null) {
            HSWanMetricsElement wm =
                    (HSWanMetricsElement) anqpElements.get(ANQPElementType.HSWANMetrics);
            if (wm != null) {
                if (wm.getStatus() != HSWanMetricsElement.LINK_STATUS_UP || wm.isCapped()) {
                    score -= WAN_PORT_DOWN_OR_CAPPED_PENALTY;
                }
            }

            IPAddressTypeAvailabilityElement ipa = (IPAddressTypeAvailabilityElement)
                    anqpElements.get(ANQPElementType.ANQPIPAddrAvailability);

            if (ipa != null) {
                Integer v4Score = IPV4_SCORES.get(ipa.getV4Availability());
                Integer v6Score = IPV6_SCORES.get(ipa.getV6Availability());
                v4Score = v4Score != null ? v4Score : 0;
                v6Score = v6Score != null ? v6Score : 0;
                score += (v4Score + v6Score);
            }
        }

        score += RSSI_SCORE.lookupScore(scanDetail.getScanResult().level, isActiveNetwork);
        return score;
    }

     * - Prefer home provider
     * - Prefer network that provides Internet access
     * - Prefer network with active WAN port with available load
     * - Prefer network that provides unrestricted IP address
     * - Prefer currently active network
     * - Prefer AP with higher RSSI

补充一下,还有网络是否私人公开也会影响

        NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.FreePublic, PUBLIC_OR_PRIVATE_NETWORK_AWARDS);
        NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.ChargeablePublic,
                PUBLIC_OR_PRIVATE_NETWORK_AWARDS);
        NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.PrivateWithGuest,
                PUBLIC_OR_PRIVATE_NETWORK_AWARDS);
        NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.Private,
                PUBLIC_OR_PRIVATE_NETWORK_AWARDS);
        NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.Personal, PERSONAL_OR_EMERGENCY_NETWORK_AWARDS);
        NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.EmergencyOnly,
                PERSONAL_OR_EMERGENCY_NETWORK_AWARDS);
        NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.Wildcard, 0);
        NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.TestOrExperimental, 0);


    /**
     * Award points for public or private network.
     */
    @VisibleForTesting
    public static final int PUBLIC_OR_PRIVATE_NETWORK_AWARDS = 4;

    /**
     * Award points for personal or emergency network.
     */
    @VisibleForTesting
    public static final int PERSONAL_OR_EMERGENCY_NETWORK_AWARDS = 2;


2.总结


passpoint自动连接

先决条件是passpoint类型的ap并且当前mAnqpCache包含

     * - Prefer home provider
     * - Prefer network that provides Internet access
     * - Prefer network with active WAN port with available load
     * - Prefer network that provides unrestricted IP address
     * - Prefer currently active network
     * - Prefer AP with higher RSSI

补充一下,还有网络类型,比如是否私人公开也会影响

 

你可能感兴趣的:(Wifi)