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