(一百六十二)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<ScanDetail> scanDetails,
                    WifiConfiguration currentNetwork, String currentBssid,
                    boolean connected, boolean untrustedNetworkAllowed,
                    List<Pair<ScanDetail, WifiConfiguration>> 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<PasspointNetworkCandidate> candidateList = new ArrayList<>();
        for (ScanDetail scanDetail : scanDetails) {
            // Skip non-Passpoint APs.
            if (!scanDetail.getNetworkDetail().isInterworking()) {
                continue;
            }

            // Find the best provider for this ScanDetail.
            Pair<PasspointProvider, PasspointMatch> 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<ANQPNetworkKey> expiredKeys = new ArrayList<>();
        for (Map.Entry<ANQPNetworkKey, ANQPData> 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<String> 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<PasspointProvider, PasspointMatch> matchProvider(ScanResult scanResult) {
        List<Pair<PasspointProvider, PasspointMatch>> allMatches = getAllMatchedProviders(
                scanResult);
        if (allMatches == null) {
            return null;
        }

        Pair<PasspointProvider, PasspointMatch> bestMatch = null;
        for (Pair<PasspointProvider, PasspointMatch> 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<Pair<PasspointProvider, PasspointMatch>> getAllMatchedProviders(
            ScanResult scanResult) {
        List<Pair<PasspointProvider, PasspointMatch>> 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<String, PasspointProvider> 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<PasspointProvider, PasspointMatch> 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<Constants.ANQPElementType, ANQPElement> 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<Byte> 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<PasspointNetworkCandidate> 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<ANQPElementType, ANQPElement> 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

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

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
十四点游戏是一款很受欢迎的数学游戏,玩家需要通过四则运算将给定的四个数字计算出24这个结果。在Android平台上开发这个游戏需要以下几个步骤。 首先,我们需要设计游戏界面。可以利用Android Studio提供的布局工具来设计一个简单直观的界面,包含四个数字输入框和一个计算按钮。玩家可以通过输入数字或者点击按钮来提交答案。 接下来,我们需要编写逻辑代码来实现游戏的规则。当玩家点击计算按钮时,我们首先需要获取四个输入框中的数字,并进行合法性检查。如果数字无效,则提示玩家重新输入。如果数字有效,则通过四则运算计算出所有可能的结果,并判断是否存在结果等于24的计算过程。如果存在结果等于24的计算过程,则提示玩家答案正确;否则,提示答案错误。 在实现四则运算的计算过程时,可以利用递归算法来穷举所有可能的组合。我们可以先从给定的四个数字中选择两个进行运算,得到一个新的数字,并将这个新的数字与另外两个数字结合进行运算,再得到一个结果。然后,将这个结果与另外两个数字进行运算,再得到一个新的结果。最后,将这个新的结果与剩下的一个数字进行运算,得到最终结果。通过递归算法,可以穷举所有可能的计算过程。 最后,我们需要添加一些额外的功能来增加游戏的趣味性。例如,可以添加计时器来记录玩家完成游戏所用的时间;也可以添加提示功能,当玩家无法找到答案时,可以点击提示按钮来获取一种可能的计算过程。这些额外功能可以通过Android的控件和事件处理来实现。 通过以上步骤,我们可以开发出一款可以玩家在Android设备上玩的十四点游戏。玩家可以通过输入数字和进行四则运算来找到计算结果等于24的过程,锻炼数学思维和逻辑能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值