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 android.net.wifi; 18 19 import android.Manifest.permission; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.content.Context; 23 import android.net.INetworkScoreCache; 24 import android.net.NetworkKey; 25 import android.net.NetworkScoreManager; 26 import android.net.ScoredNetwork; 27 import android.os.Handler; 28 import android.os.Process; 29 import android.util.Log; 30 import android.util.LruCache; 31 32 import com.android.internal.annotations.GuardedBy; 33 import com.android.internal.util.Preconditions; 34 35 import java.io.FileDescriptor; 36 import java.io.PrintWriter; 37 import java.util.List; 38 39 /** 40 * {@link INetworkScoreCache} implementation for Wifi Networks. 41 * 42 * @hide 43 */ 44 public class WifiNetworkScoreCache extends INetworkScoreCache.Stub 45 implements NetworkScoreManager.NetworkScoreCallback { 46 private static final String TAG = "WifiNetworkScoreCache"; 47 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 48 49 // A Network scorer returns a score in the range [-128, +127] 50 // We treat the lowest possible score as though there were no score, effectively allowing the 51 // scorer to provide an RSSI threshold below which a network should not be used. 52 public static final int INVALID_NETWORK_SCORE = Byte.MIN_VALUE; 53 54 /** Default number entries to be stored in the {@link LruCache}. */ 55 private static final int DEFAULT_MAX_CACHE_SIZE = 100; 56 57 // See {@link #CacheListener}. 58 @Nullable 59 @GuardedBy("mLock") 60 private CacheListener mListener; 61 62 private final Context mContext; 63 private final Object mLock = new Object(); 64 65 // The key is of the form "<ssid>"<bssid> 66 // TODO: What about SSIDs that can't be encoded as UTF-8? 67 @GuardedBy("mLock") 68 private final LruCache<String, ScoredNetwork> mCache; 69 WifiNetworkScoreCache(Context context)70 public WifiNetworkScoreCache(Context context) { 71 this(context, null /* listener */); 72 } 73 74 /** 75 * Instantiates a WifiNetworkScoreCache. 76 * 77 * @param context Application context 78 * @param listener CacheListener for cache updates 79 */ WifiNetworkScoreCache(Context context, @Nullable CacheListener listener)80 public WifiNetworkScoreCache(Context context, @Nullable CacheListener listener) { 81 this(context, listener, DEFAULT_MAX_CACHE_SIZE); 82 } 83 WifiNetworkScoreCache( Context context, @Nullable CacheListener listener, int maxCacheSize)84 public WifiNetworkScoreCache( 85 Context context, @Nullable CacheListener listener, int maxCacheSize) { 86 mContext = context.getApplicationContext(); 87 mListener = listener; 88 mCache = new LruCache<>(maxCacheSize); 89 } 90 updateScores(List<ScoredNetwork> networks)91 @Override public final void updateScores(List<ScoredNetwork> networks) { 92 if (networks == null || networks.isEmpty()) { 93 return; 94 } 95 if (DBG) { 96 Log.d(TAG, "updateScores list size=" + networks.size()); 97 } 98 99 boolean changed = false; 100 101 synchronized(mLock) { 102 for (ScoredNetwork network : networks) { 103 String networkKey = buildNetworkKey(network); 104 if (networkKey == null) { 105 if (DBG) { 106 Log.d(TAG, "Failed to build network key for ScoredNetwork" + network); 107 } 108 continue; 109 } 110 mCache.put(networkKey, network); 111 changed = true; 112 } 113 114 if (mListener != null && changed) { 115 mListener.post(networks); 116 } 117 } 118 } 119 clearScores()120 @Override public final void clearScores() { 121 synchronized (mLock) { 122 mCache.evictAll(); 123 } 124 } 125 126 /** 127 * Returns whether there is any score info for the given ScanResult. 128 * 129 * This includes null-score info, so it should only be used when determining whether to request 130 * scores from the network scorer. 131 */ isScoredNetwork(ScanResult result)132 public boolean isScoredNetwork(ScanResult result) { 133 return getScoredNetwork(result) != null; 134 } 135 136 /** 137 * Returns whether there is a non-null score curve for the given ScanResult. 138 * 139 * A null score curve has special meaning - we should never connect to an ephemeral network if 140 * the score curve is null. 141 */ hasScoreCurve(ScanResult result)142 public boolean hasScoreCurve(ScanResult result) { 143 ScoredNetwork network = getScoredNetwork(result); 144 return network != null && network.rssiCurve != null; 145 } 146 getNetworkScore(ScanResult result)147 public int getNetworkScore(ScanResult result) { 148 int score = INVALID_NETWORK_SCORE; 149 150 ScoredNetwork network = getScoredNetwork(result); 151 if (network != null && network.rssiCurve != null) { 152 score = network.rssiCurve.lookupScore(result.level); 153 if (DBG) { 154 Log.d(TAG, "getNetworkScore found scored network " + network.networkKey 155 + " score " + Integer.toString(score) 156 + " RSSI " + result.level); 157 } 158 } 159 return score; 160 } 161 162 /** 163 * Returns the ScoredNetwork metered hint for a given ScanResult. 164 * 165 * If there is no ScoredNetwork associated with the ScanResult then false will be returned. 166 */ getMeteredHint(ScanResult result)167 public boolean getMeteredHint(ScanResult result) { 168 ScoredNetwork network = getScoredNetwork(result); 169 return network != null && network.meteredHint; 170 } 171 getNetworkScore(ScanResult result, boolean isActiveNetwork)172 public int getNetworkScore(ScanResult result, boolean isActiveNetwork) { 173 int score = INVALID_NETWORK_SCORE; 174 175 ScoredNetwork network = getScoredNetwork(result); 176 if (network != null && network.rssiCurve != null) { 177 score = network.rssiCurve.lookupScore(result.level, isActiveNetwork); 178 if (DBG) { 179 Log.d(TAG, "getNetworkScore found scored network " + network.networkKey 180 + " score " + Integer.toString(score) 181 + " RSSI " + result.level 182 + " isActiveNetwork " + isActiveNetwork); 183 } 184 } 185 return score; 186 } 187 188 @Nullable getScoredNetwork(ScanResult result)189 public ScoredNetwork getScoredNetwork(ScanResult result) { 190 String key = buildNetworkKey(result); 191 if (key == null) return null; 192 193 synchronized(mLock) { 194 ScoredNetwork network = mCache.get(key); 195 return network; 196 } 197 } 198 199 /** Returns the ScoredNetwork for the given key. */ 200 @Nullable getScoredNetwork(NetworkKey networkKey)201 public ScoredNetwork getScoredNetwork(NetworkKey networkKey) { 202 String key = buildNetworkKey(networkKey); 203 if (key == null) { 204 if (DBG) { 205 Log.d(TAG, "Could not build key string for Network Key: " + networkKey); 206 } 207 return null; 208 } 209 synchronized (mLock) { 210 return mCache.get(key); 211 } 212 } 213 buildNetworkKey(ScoredNetwork network)214 private String buildNetworkKey(ScoredNetwork network) { 215 if (network == null) { 216 return null; 217 } 218 return buildNetworkKey(network.networkKey); 219 } 220 buildNetworkKey(NetworkKey networkKey)221 private String buildNetworkKey(NetworkKey networkKey) { 222 if (networkKey == null) { 223 return null; 224 } 225 if (networkKey.wifiKey == null) return null; 226 if (networkKey.type == NetworkKey.TYPE_WIFI) { 227 String key = networkKey.wifiKey.ssid; 228 if (key == null) return null; 229 if (networkKey.wifiKey.bssid != null) { 230 key = key + networkKey.wifiKey.bssid; 231 } 232 return key; 233 } 234 return null; 235 } 236 buildNetworkKey(ScanResult result)237 private String buildNetworkKey(ScanResult result) { 238 if (result == null || result.SSID == null) { 239 return null; 240 } 241 StringBuilder key = new StringBuilder("\""); 242 key.append(result.SSID); 243 key.append("\""); 244 if (result.BSSID != null) { 245 key.append(result.BSSID); 246 } 247 return key.toString(); 248 } 249 dump(FileDescriptor fd, PrintWriter writer, String[] args)250 @Override protected final void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 251 WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); 252 dumpWithLatestScanResults(fd, writer, args, wifiManager.getScanResults()); 253 } 254 255 /** 256 * This is directly invoked from within Wifi-Service (on it's instance of this class), hence 257 * avoid making the WifiManager.getScanResults() call to avoid a deadlock. 258 */ dumpWithLatestScanResults( FileDescriptor fd, PrintWriter writer, String[] args, List<ScanResult> latestScanResults)259 public final void dumpWithLatestScanResults( 260 FileDescriptor fd, PrintWriter writer, String[] args, 261 List<ScanResult> latestScanResults) { 262 mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG); 263 String header = String.format("WifiNetworkScoreCache (%s/%d)", 264 mContext.getPackageName(), Process.myUid()); 265 writer.println(header); 266 writer.println(" All score curves:"); 267 synchronized (mLock) { 268 for (ScoredNetwork score : mCache.snapshot().values()) { 269 writer.println(" " + score); 270 } 271 writer.println(" Network scores for latest ScanResults:"); 272 for (ScanResult scanResult : latestScanResults) { 273 writer.println( 274 " " + buildNetworkKey(scanResult) + ": " + getNetworkScore(scanResult)); 275 } 276 } 277 } 278 279 /** Registers a CacheListener instance, replacing the previous listener if it existed. */ registerListener(CacheListener listener)280 public void registerListener(CacheListener listener) { 281 synchronized (mLock) { 282 mListener = listener; 283 } 284 } 285 286 /** Removes the registered CacheListener. */ unregisterListener()287 public void unregisterListener() { 288 synchronized (mLock) { 289 mListener = null; 290 } 291 } 292 293 /** Listener for updates to the cache inside WifiNetworkScoreCache. */ 294 public abstract static class CacheListener { 295 private Handler mHandler; 296 297 /** 298 * Constructor for CacheListener. 299 * 300 * @param handler the Handler on which to invoke the {@link #networkCacheUpdated} method. 301 * This cannot be null. 302 */ CacheListener(@onNull Handler handler)303 public CacheListener(@NonNull Handler handler) { 304 Preconditions.checkNotNull(handler); 305 mHandler = handler; 306 } 307 308 /** Invokes the {@link #networkCacheUpdated(List<ScoredNetwork>)} method on the handler. */ post(List<ScoredNetwork> updatedNetworks)309 void post(List<ScoredNetwork> updatedNetworks) { 310 mHandler.post(new Runnable() { 311 @Override 312 public void run() { 313 networkCacheUpdated(updatedNetworks); 314 } 315 }); 316 } 317 318 /** 319 * Invoked whenever the cache is updated. 320 * 321 * <p>Clearing the cache does not invoke this method. 322 * 323 * @param updatedNetworks the networks that were updated 324 */ networkCacheUpdated(List<ScoredNetwork> updatedNetworks)325 public abstract void networkCacheUpdated(List<ScoredNetwork> updatedNetworks); 326 } 327 } 328