1/*
2 * Copyright (C) 2014 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;
18
19import android.Manifest.permission;
20import android.content.Context;
21import android.net.INetworkScoreCache;
22import android.net.NetworkKey;
23import android.net.ScoredNetwork;
24import android.net.wifi.ScanResult;
25import android.net.wifi.WifiManager;
26import android.util.Log;
27
28import java.io.FileDescriptor;
29import java.io.PrintWriter;
30import java.util.HashMap;
31import java.util.List;
32import java.util.Map;
33
34public class WifiNetworkScoreCache extends INetworkScoreCache.Stub {
35    private static final String TAG = "WifiNetworkScoreCache";
36    private static final boolean DBG = false;
37
38    // A Network scorer returns a score in the range [-128, +127]
39    // We treat the lowest possible score as though there were no score, effectively allowing the
40    // scorer to provide an RSSI threshold below which a network should not be used.
41    public static final int INVALID_NETWORK_SCORE = Byte.MIN_VALUE;
42    private final Context mContext;
43
44    // The key is of the form "<ssid>"<bssid>
45    // TODO: What about SSIDs that can't be encoded as UTF-8?
46    private final Map<String, ScoredNetwork> mNetworkCache;
47
48    public WifiNetworkScoreCache(Context context) {
49        mContext = context;
50        mNetworkCache = new HashMap<String, ScoredNetwork>();
51    }
52
53     @Override public final void updateScores(List<android.net.ScoredNetwork> networks) {
54        if (networks == null) {
55            return;
56        }
57        Log.e(TAG, "updateScores list size=" + networks.size());
58
59        synchronized(mNetworkCache) {
60            for (ScoredNetwork network : networks) {
61                String networkKey = buildNetworkKey(network);
62                if (networkKey == null) continue;
63                mNetworkCache.put(networkKey, network);
64            }
65        }
66     }
67
68     @Override public final void clearScores() {
69         synchronized (mNetworkCache) {
70             mNetworkCache.clear();
71         }
72     }
73
74    /**
75     * Returns whether there is any score info for the given ScanResult.
76     *
77     * This includes null-score info, so it should only be used when determining whether to request
78     * scores from the network scorer.
79     */
80    public boolean isScoredNetwork(ScanResult result) {
81        return getScoredNetwork(result) != null;
82    }
83
84    /**
85     * Returns whether there is a non-null score curve for the given ScanResult.
86     *
87     * A null score curve has special meaning - we should never connect to an ephemeral network if
88     * the score curve is null.
89     */
90    public boolean hasScoreCurve(ScanResult result) {
91        ScoredNetwork network = getScoredNetwork(result);
92        return network != null && network.rssiCurve != null;
93    }
94
95    public int getNetworkScore(ScanResult result) {
96
97        int score = INVALID_NETWORK_SCORE;
98
99        ScoredNetwork network = getScoredNetwork(result);
100        if (network != null && network.rssiCurve != null) {
101            score = network.rssiCurve.lookupScore(result.level);
102            if (DBG) {
103                Log.e(TAG, "getNetworkScore found scored network " + network.networkKey
104                        + " score " + Integer.toString(score)
105                        + " RSSI " + result.level);
106            }
107        }
108        return score;
109    }
110
111    /**
112     * Returns the ScoredNetwork metered hint for a given ScanResult.
113     *
114     * If there is no ScoredNetwork associated with the ScanResult then false will be returned.
115     */
116    public boolean getMeteredHint(ScanResult result) {
117        ScoredNetwork network = getScoredNetwork(result);
118        return network != null && network.meteredHint;
119    }
120
121    public int getNetworkScore(ScanResult result, boolean isActiveNetwork) {
122
123        int score = INVALID_NETWORK_SCORE;
124
125        ScoredNetwork network = getScoredNetwork(result);
126        if (network != null && network.rssiCurve != null) {
127            score = network.rssiCurve.lookupScore(result.level, isActiveNetwork);
128            if (DBG) {
129                Log.e(TAG, "getNetworkScore found scored network " + network.networkKey
130                        + " score " + Integer.toString(score)
131                        + " RSSI " + result.level
132                        + " isActiveNetwork " + isActiveNetwork);
133            }
134        }
135        return score;
136    }
137
138    private ScoredNetwork getScoredNetwork(ScanResult result) {
139        String key = buildNetworkKey(result);
140        if (key == null) return null;
141
142        //find it
143        synchronized(mNetworkCache) {
144            ScoredNetwork network = mNetworkCache.get(key);
145            return network;
146        }
147    }
148
149     private String buildNetworkKey(ScoredNetwork network) {
150        if (network == null || network.networkKey == null) return null;
151        if (network.networkKey.wifiKey == null) return null;
152        if (network.networkKey.type == NetworkKey.TYPE_WIFI) {
153            String key = network.networkKey.wifiKey.ssid;
154            if (key == null) return null;
155            if (network.networkKey.wifiKey.bssid != null) {
156                key = key + network.networkKey.wifiKey.bssid;
157            }
158            return key;
159        }
160        return null;
161    }
162
163    private String buildNetworkKey(ScanResult result) {
164        if (result == null || result.SSID == null) {
165            return null;
166        }
167        StringBuilder key = new StringBuilder("\"");
168        key.append(result.SSID);
169        key.append("\"");
170        if (result.BSSID != null) {
171            key.append(result.BSSID);
172        }
173        return key.toString();
174    }
175
176    @Override protected final void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
177        mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
178        writer.println("WifiNetworkScoreCache");
179        writer.println("  All score curves:");
180        for (Map.Entry<String, ScoredNetwork> entry : mNetworkCache.entrySet()) {
181            ScoredNetwork scoredNetwork = entry.getValue();
182            writer.println("    " + entry.getKey() + ": " + scoredNetwork.rssiCurve
183                    + ", meteredHint=" + scoredNetwork.meteredHint);
184        }
185        writer.println("  Current network scores:");
186        WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
187        for (ScanResult scanResult : wifiManager.getScanResults()) {
188            writer.println("    " + buildNetworkKey(scanResult) + ": " + getNetworkScore(scanResult));
189        }
190    }
191
192}
193