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 
17 package com.android.server.wifi;
18 
19 import android.Manifest.permission;
20 import android.content.Context;
21 import android.net.INetworkScoreCache;
22 import android.net.NetworkKey;
23 import android.net.ScoredNetwork;
24 import android.net.wifi.ScanResult;
25 import android.net.wifi.WifiManager;
26 import android.util.Log;
27 
28 import java.io.FileDescriptor;
29 import java.io.PrintWriter;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33 
34 public 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 
WifiNetworkScoreCache(Context context)48     public WifiNetworkScoreCache(Context context) {
49         mContext = context;
50         mNetworkCache = new HashMap<String, ScoredNetwork>();
51     }
52 
updateScores(List<android.net.ScoredNetwork> networks)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 
clearScores()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      */
isScoredNetwork(ScanResult result)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      */
hasScoreCurve(ScanResult result)90     public boolean hasScoreCurve(ScanResult result) {
91         ScoredNetwork network = getScoredNetwork(result);
92         return network != null && network.rssiCurve != null;
93     }
94 
getNetworkScore(ScanResult result)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      */
getMeteredHint(ScanResult result)116     public boolean getMeteredHint(ScanResult result) {
117         ScoredNetwork network = getScoredNetwork(result);
118         return network != null && network.meteredHint;
119     }
120 
getNetworkScore(ScanResult result, boolean isActiveNetwork)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 
getScoredNetwork(ScanResult result)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 
buildNetworkKey(ScoredNetwork network)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 
buildNetworkKey(ScanResult result)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 
dump(FileDescriptor fd, PrintWriter writer, String[] args)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