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