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