1 /* 2 * Copyright (C) 2019 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.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.content.Context; 23 import android.net.NetworkKey; 24 import android.net.NetworkScoreManager; 25 import android.net.ScoredNetwork; 26 import android.net.wifi.ScanResult; 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.ArrayList; 38 import java.util.Collection; 39 import java.util.List; 40 41 /** 42 * {@link NetworkScoreManager.NetworkScoreCallback} implementation for Wifi Networks. 43 * 44 * Note: This is a copy of WifiNetworkScoreCache for internal usage by the wifi stack. This extends 45 * the formal API: {@link NetworkScoreManager.NetworkScoreCallback} 46 */ 47 public class WifiNetworkScoreCache extends NetworkScoreManager.NetworkScoreCallback { 48 private static final String TAG = "WifiNetworkScoreCache"; 49 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 50 51 // A Network scorer returns a score in the range [-128, +127] 52 // We treat the lowest possible score as though there were no score, effectively allowing the 53 // scorer to provide an RSSI threshold below which a network should not be used. 54 public static final int INVALID_NETWORK_SCORE = Byte.MIN_VALUE; 55 56 /** Default number entries to be stored in the {@link LruCache}. */ 57 private static final int DEFAULT_MAX_CACHE_SIZE = 100; 58 59 // See {@link #CacheListener}. 60 @Nullable 61 @GuardedBy("mLock") 62 private CacheListener mListener; 63 64 private final Context mContext; 65 private final Object mLock = new Object(); 66 67 // The key is of the form "<ssid>"<bssid> 68 // TODO: What about SSIDs that can't be encoded as UTF-8? 69 @GuardedBy("mLock") 70 private final LruCache<String, ScoredNetwork> mCache; 71 WifiNetworkScoreCache(Context context)72 public WifiNetworkScoreCache(Context context) { 73 this(context, null /* listener */); 74 } 75 76 /** 77 * Instantiates a WifiNetworkScoreCache. 78 * 79 * @param context Application context 80 * @param listener CacheListener for cache updates 81 */ WifiNetworkScoreCache(Context context, @Nullable CacheListener listener)82 public WifiNetworkScoreCache(Context context, @Nullable CacheListener listener) { 83 this(context, listener, DEFAULT_MAX_CACHE_SIZE); 84 } 85 WifiNetworkScoreCache( Context context, @Nullable CacheListener listener, int maxCacheSize)86 public WifiNetworkScoreCache( 87 Context context, @Nullable CacheListener listener, int maxCacheSize) { 88 mContext = context.getApplicationContext(); 89 mListener = listener; 90 mCache = new LruCache<>(maxCacheSize); 91 } 92 93 @Override onScoresUpdated(Collection<ScoredNetwork> networks)94 public final void onScoresUpdated(Collection<ScoredNetwork> networks) { 95 if (networks == null || networks.isEmpty()) { 96 return; 97 } 98 if (DBG) { 99 Log.d(TAG, "updateScores list size=" + networks.size()); 100 } 101 102 boolean changed = false; 103 104 synchronized (mLock) { 105 for (ScoredNetwork network : networks) { 106 String networkKey = buildNetworkKey(network); 107 if (networkKey == null) { 108 if (DBG) { 109 Log.d(TAG, "Failed to build network key for ScoredNetwork" + network); 110 } 111 continue; 112 } 113 mCache.put(networkKey, network); 114 changed = true; 115 } 116 117 if (mListener != null && changed) { 118 mListener.post(new ArrayList<>(networks)); 119 } 120 } 121 } 122 123 @Override onScoresInvalidated()124 public final void onScoresInvalidated() { 125 synchronized (mLock) { 126 mCache.evictAll(); 127 } 128 } 129 130 /** 131 * Returns whether there is any score info for the given ScanResult. 132 * 133 * This includes null-score info, so it should only be used when determining whether to request 134 * scores from the network scorer. 135 */ isScoredNetwork(ScanResult result)136 public boolean isScoredNetwork(ScanResult result) { 137 return getScoredNetwork(result) != null; 138 } 139 140 /** 141 * Returns whether there is a non-null score curve for the given ScanResult. 142 * 143 * A null score curve has special meaning - we should never connect to an ephemeral network if 144 * the score curve is null. 145 */ hasScoreCurve(ScanResult result)146 public boolean hasScoreCurve(ScanResult result) { 147 ScoredNetwork network = getScoredNetwork(result); 148 return network != null && network.rssiCurve != null; 149 } 150 getNetworkScore(ScanResult result)151 private int getNetworkScore(ScanResult result) { 152 int score = INVALID_NETWORK_SCORE; 153 154 ScoredNetwork network = getScoredNetwork(result); 155 if (network != null && network.rssiCurve != null) { 156 score = network.rssiCurve.lookupScore(result.level); 157 if (DBG) { 158 Log.d(TAG, "getNetworkScore found scored network " + network.networkKey 159 + " score " + Integer.toString(score) 160 + " RSSI " + result.level); 161 } 162 } 163 return score; 164 } 165 166 /** 167 * Returns the ScoredNetwork metered hint for a given ScanResult. 168 * 169 * If there is no ScoredNetwork associated with the ScanResult then false will be returned. 170 */ getMeteredHint(ScanResult result)171 public boolean getMeteredHint(ScanResult result) { 172 ScoredNetwork network = getScoredNetwork(result); 173 return network != null && network.meteredHint; 174 } 175 176 /** 177 * Get network score for the provided ScanResult. 178 */ getNetworkScore(ScanResult result, boolean isActiveNetwork)179 public int getNetworkScore(ScanResult result, boolean isActiveNetwork) { 180 int score = INVALID_NETWORK_SCORE; 181 182 ScoredNetwork network = getScoredNetwork(result); 183 if (network != null && network.rssiCurve != null) { 184 score = network.rssiCurve.lookupScore(result.level, isActiveNetwork); 185 if (DBG) { 186 Log.d(TAG, "getNetworkScore found scored network " + network.networkKey 187 + " score " + Integer.toString(score) 188 + " RSSI " + result.level 189 + " isActiveNetwork " + isActiveNetwork); 190 } 191 } 192 return score; 193 } 194 195 @Nullable getScoredNetwork(ScanResult result)196 private ScoredNetwork getScoredNetwork(ScanResult result) { 197 String key = buildNetworkKey(result); 198 if (key == null) return null; 199 200 synchronized (mLock) { 201 ScoredNetwork network = mCache.get(key); 202 return network; 203 } 204 } 205 206 /** Returns the ScoredNetwork for the given key. */ 207 @Nullable getScoredNetwork(NetworkKey networkKey)208 public ScoredNetwork getScoredNetwork(NetworkKey networkKey) { 209 String key = buildNetworkKey(networkKey); 210 if (key == null) { 211 if (DBG) { 212 Log.d(TAG, "Could not build key string for Network Key: " + networkKey); 213 } 214 return null; 215 } 216 synchronized (mLock) { 217 return mCache.get(key); 218 } 219 } 220 buildNetworkKey(ScoredNetwork network)221 private String buildNetworkKey(ScoredNetwork network) { 222 if (network == null) { 223 return null; 224 } 225 return buildNetworkKey(network.networkKey); 226 } 227 buildNetworkKey(NetworkKey networkKey)228 private String buildNetworkKey(NetworkKey networkKey) { 229 if (networkKey == null) { 230 return null; 231 } 232 if (networkKey.wifiKey == null) return null; 233 if (networkKey.type == NetworkKey.TYPE_WIFI) { 234 String key = networkKey.wifiKey.ssid; 235 if (key == null) return null; 236 if (networkKey.wifiKey.bssid != null) { 237 key = key + networkKey.wifiKey.bssid; 238 } 239 return key; 240 } 241 return null; 242 } 243 buildNetworkKey(ScanResult result)244 private String buildNetworkKey(ScanResult result) { 245 if (result == null || result.SSID == null) { 246 return null; 247 } 248 StringBuilder key = new StringBuilder("\""); 249 key.append(result.SSID); 250 key.append("\""); 251 if (result.BSSID != null) { 252 key.append(result.BSSID); 253 } 254 return key.toString(); 255 } 256 257 /** 258 * This is directly invoked from within Wifi-Service (on it's instance of this class), hence 259 * avoid making the WifiManager.getScanResults() call to avoid a deadlock. 260 */ dumpWithLatestScanResults( FileDescriptor fd, PrintWriter writer, String[] args, List<ScanResult> latestScanResults)261 public final void dumpWithLatestScanResults( 262 FileDescriptor fd, PrintWriter writer, String[] args, 263 List<ScanResult> latestScanResults) { 264 mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG); 265 String header = String.format("WifiNetworkScoreCache (%s/%d)", 266 mContext.getPackageName(), Process.myUid()); 267 writer.println(header); 268 writer.println(" All score curves:"); 269 synchronized (mLock) { 270 for (ScoredNetwork score : mCache.snapshot().values()) { 271 writer.println(" " + score); 272 } 273 writer.println(" Network scores for latest ScanResults:"); 274 for (ScanResult scanResult : latestScanResults) { 275 writer.println( 276 " " + buildNetworkKey(scanResult) + ": " + getNetworkScore(scanResult)); 277 } 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