PasspointNetworkEvaluator.java revision e7dfd69fbe21902b89124b99d914ed3c90ba8baf
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.wifi.hotspot2;
18
19import android.net.wifi.WifiConfiguration;
20import android.os.Process;
21import android.text.TextUtils;
22import android.util.LocalLog;
23import android.util.Pair;
24
25import com.android.server.wifi.NetworkUpdateResult;
26import com.android.server.wifi.ScanDetail;
27import com.android.server.wifi.WifiConfigManager;
28import com.android.server.wifi.WifiNetworkSelector;
29import com.android.server.wifi.util.ScanResultUtil;
30
31import java.util.ArrayList;
32import java.util.List;
33
34/**
35 * This class is the WifiNetworkSelector.NetworkEvaluator implementation for
36 * Passpoint networks.
37 */
38public class PasspointNetworkEvaluator implements WifiNetworkSelector.NetworkEvaluator {
39    private static final String NAME = "PasspointNetworkEvaluator";
40
41    private final PasspointManager mPasspointManager;
42    private final WifiConfigManager mWifiConfigManager;
43    private final LocalLog mLocalLog;
44
45    /**
46     * Contained information for a Passpoint network candidate.
47     */
48    private class PasspointNetworkCandidate {
49        PasspointNetworkCandidate(PasspointProvider provider, PasspointMatch matchStatus,
50                ScanDetail scanDetail) {
51            mProvider = provider;
52            mMatchStatus = matchStatus;
53            mScanDetail = scanDetail;
54        }
55        PasspointProvider mProvider;
56        PasspointMatch mMatchStatus;
57        ScanDetail mScanDetail;
58    }
59
60    public PasspointNetworkEvaluator(PasspointManager passpointManager,
61            WifiConfigManager wifiConfigManager, LocalLog localLog) {
62        mPasspointManager = passpointManager;
63        mWifiConfigManager = wifiConfigManager;
64        mLocalLog = localLog;
65    }
66
67    @Override
68    public String getName() {
69        return NAME;
70    }
71
72    @Override
73    public void update(List<ScanDetail> scanDetails) {}
74
75    @Override
76    public WifiConfiguration evaluateNetworks(List<ScanDetail> scanDetails,
77                    WifiConfiguration currentNetwork, String currentBssid,
78                    boolean connected, boolean untrustedNetworkAllowed,
79                    List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks) {
80        // Sweep the ANQP cache to remove any expired ANQP entries.
81        mPasspointManager.sweepCache();
82
83        // Go through each ScanDetail and find the best provider for each ScanDetail.
84        List<PasspointNetworkCandidate> candidateList = new ArrayList<>();
85        for (ScanDetail scanDetail : scanDetails) {
86            // Skip non-Passpoint APs.
87            if (!scanDetail.getNetworkDetail().isInterworking()) {
88                continue;
89            }
90
91            // Find the best provider for this ScanDetail.
92            Pair<PasspointProvider, PasspointMatch> bestProvider =
93                    mPasspointManager.matchProvider(scanDetail.getScanResult());
94            if (bestProvider != null) {
95                if (bestProvider.first.isSimCredential() && !mWifiConfigManager.isSimPresent()) {
96                    // Skip providers backed by SIM credential when SIM is not present.
97                    continue;
98                }
99                candidateList.add(new PasspointNetworkCandidate(
100                        bestProvider.first, bestProvider.second, scanDetail));
101            }
102        }
103
104        // Done if no candidate is found.
105        if (candidateList.isEmpty()) {
106            localLog("No suitable Passpoint network found");
107            return null;
108        }
109
110        // Find the best Passpoint network among all candidates.
111        PasspointNetworkCandidate bestNetwork =
112                findBestNetwork(candidateList, currentNetwork == null ? null : currentNetwork.SSID);
113
114        // Return the configuration for the current connected network if it is the best network.
115        if (currentNetwork != null && TextUtils.equals(currentNetwork.SSID,
116                ScanResultUtil.createQuotedSSID(bestNetwork.mScanDetail.getSSID()))) {
117            localLog("Staying with current Passpoint network " + currentNetwork.SSID);
118            connectableNetworks.add(Pair.create(bestNetwork.mScanDetail, currentNetwork));
119            return currentNetwork;
120        }
121
122        WifiConfiguration config = createWifiConfigForProvider(bestNetwork);
123        connectableNetworks.add(Pair.create(bestNetwork.mScanDetail, config));
124        localLog("Passpoint network to connect to: " + config.SSID);
125        return config;
126    }
127
128    /**
129     * Create and return a WifiConfiguration for the given ScanDetail and PasspointProvider.
130     * The newly created WifiConfiguration will also be added to WifiConfigManager.
131     *
132     * @param networkInfo Contained information for the Passpoint network to connect to
133     * @return {@link WifiConfiguration}
134     */
135    private WifiConfiguration createWifiConfigForProvider(PasspointNetworkCandidate networkInfo) {
136        WifiConfiguration config = networkInfo.mProvider.getWifiConfig();
137        config.SSID = ScanResultUtil.createQuotedSSID(networkInfo.mScanDetail.getSSID());
138        if (networkInfo.mMatchStatus == PasspointMatch.HomeProvider) {
139            config.isHomeProviderNetwork = true;
140        }
141
142        // Add the newly created WifiConfiguration to WifiConfigManager.
143        NetworkUpdateResult result =
144                mWifiConfigManager.addOrUpdateNetwork(config, Process.WIFI_UID);
145        if (!result.isSuccess()) {
146            localLog("Failed to add passpoint network");
147            return null;
148        }
149        mWifiConfigManager.enableNetwork(result.getNetworkId(), false, Process.WIFI_UID);
150        mWifiConfigManager.setNetworkCandidateScanResult(result.getNetworkId(),
151                networkInfo.mScanDetail.getScanResult(), 0);
152        mWifiConfigManager.updateScanDetailForNetwork(
153                result.getNetworkId(), networkInfo.mScanDetail);
154        return mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
155    }
156
157    /**
158     * Given a list of Passpoint networks (with both provider and scan info), find and return
159     * the one with highest score.  The score is calculated using
160     * {@link PasspointNetworkScore#calculateScore}.
161     *
162     * @param networkList List of Passpoint networks
163     * @param currentNetworkSsid The SSID of the currently connected network, null if not connected
164     * @return {@link PasspointNetworkCandidate}
165     */
166    private PasspointNetworkCandidate findBestNetwork(
167            List<PasspointNetworkCandidate> networkList, String currentNetworkSsid) {
168        PasspointNetworkCandidate bestCandidate = null;
169        int bestScore = Integer.MIN_VALUE;
170        for (PasspointNetworkCandidate candidate : networkList) {
171            ScanDetail scanDetail = candidate.mScanDetail;
172            PasspointMatch match = candidate.mMatchStatus;
173
174            boolean isActiveNetwork = TextUtils.equals(currentNetworkSsid,
175                    ScanResultUtil.createQuotedSSID(scanDetail.getSSID()));
176            int score = PasspointNetworkScore.calculateScore(match == PasspointMatch.HomeProvider,
177                    scanDetail, mPasspointManager.getANQPElements(scanDetail.getScanResult()),
178                    isActiveNetwork);
179
180            if (score > bestScore) {
181                bestCandidate = candidate;
182                bestScore = score;
183            }
184        }
185        localLog("Best Passpoint network " + bestCandidate.mScanDetail.getSSID() + " provided by "
186                + bestCandidate.mProvider.getConfig().getHomeSp().getFqdn());
187        return bestCandidate;
188    }
189
190    private void localLog(String log) {
191        mLocalLog.log(log);
192    }
193}
194