| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.net.wifi; |
| |
| import android.Manifest.permission; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.content.Context; |
| import android.net.INetworkScoreCache; |
| import android.net.NetworkKey; |
| import android.net.ScoredNetwork; |
| import android.os.Handler; |
| import android.os.Process; |
| import android.util.Log; |
| import android.util.LruCache; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.util.Preconditions; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.List; |
| |
| /** |
| * {@link INetworkScoreCache} implementation for Wifi Networks. |
| * |
| * TODO: This should not be part of wifi mainline module. |
| * @hide |
| */ |
| public class WifiNetworkScoreCache extends INetworkScoreCache.Stub { |
| private static final String TAG = "WifiNetworkScoreCache"; |
| private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); |
| |
| // A Network scorer returns a score in the range [-128, +127] |
| // We treat the lowest possible score as though there were no score, effectively allowing the |
| // scorer to provide an RSSI threshold below which a network should not be used. |
| public static final int INVALID_NETWORK_SCORE = Byte.MIN_VALUE; |
| |
| /** Default number entries to be stored in the {@link LruCache}. */ |
| private static final int DEFAULT_MAX_CACHE_SIZE = 100; |
| |
| // See {@link #CacheListener}. |
| @Nullable |
| @GuardedBy("mLock") |
| private CacheListener mListener; |
| |
| private final Context mContext; |
| private final Object mLock = new Object(); |
| |
| // The key is of the form "<ssid>"<bssid> |
| // TODO: What about SSIDs that can't be encoded as UTF-8? |
| @GuardedBy("mLock") |
| private final LruCache<String, ScoredNetwork> mCache; |
| |
| public WifiNetworkScoreCache(Context context) { |
| this(context, null /* listener */); |
| } |
| |
| /** |
| * Instantiates a WifiNetworkScoreCache. |
| * |
| * @param context Application context |
| * @param listener CacheListener for cache updates |
| */ |
| public WifiNetworkScoreCache(Context context, @Nullable CacheListener listener) { |
| this(context, listener, DEFAULT_MAX_CACHE_SIZE); |
| } |
| |
| public WifiNetworkScoreCache( |
| Context context, @Nullable CacheListener listener, int maxCacheSize) { |
| mContext = context.getApplicationContext(); |
| mListener = listener; |
| mCache = new LruCache<>(maxCacheSize); |
| } |
| |
| @Override public final void updateScores(List<ScoredNetwork> networks) { |
| if (networks == null || networks.isEmpty()) { |
| return; |
| } |
| if (DBG) { |
| Log.d(TAG, "updateScores list size=" + networks.size()); |
| } |
| |
| boolean changed = false; |
| |
| synchronized(mLock) { |
| for (ScoredNetwork network : networks) { |
| String networkKey = buildNetworkKey(network); |
| if (networkKey == null) { |
| if (DBG) { |
| Log.d(TAG, "Failed to build network key for ScoredNetwork" + network); |
| } |
| continue; |
| } |
| mCache.put(networkKey, network); |
| changed = true; |
| } |
| |
| if (mListener != null && changed) { |
| mListener.post(networks); |
| } |
| } |
| } |
| |
| @Override public final void clearScores() { |
| synchronized (mLock) { |
| mCache.evictAll(); |
| } |
| } |
| |
| /** |
| * Returns whether there is any score info for the given ScanResult. |
| * |
| * This includes null-score info, so it should only be used when determining whether to request |
| * scores from the network scorer. |
| */ |
| public boolean isScoredNetwork(ScanResult result) { |
| return getScoredNetwork(result) != null; |
| } |
| |
| /** |
| * Returns whether there is a non-null score curve for the given ScanResult. |
| * |
| * A null score curve has special meaning - we should never connect to an ephemeral network if |
| * the score curve is null. |
| */ |
| public boolean hasScoreCurve(ScanResult result) { |
| ScoredNetwork network = getScoredNetwork(result); |
| return network != null && network.rssiCurve != null; |
| } |
| |
| public int getNetworkScore(ScanResult result) { |
| int score = INVALID_NETWORK_SCORE; |
| |
| ScoredNetwork network = getScoredNetwork(result); |
| if (network != null && network.rssiCurve != null) { |
| score = network.rssiCurve.lookupScore(result.level); |
| if (DBG) { |
| Log.d(TAG, "getNetworkScore found scored network " + network.networkKey |
| + " score " + Integer.toString(score) |
| + " RSSI " + result.level); |
| } |
| } |
| return score; |
| } |
| |
| /** |
| * Returns the ScoredNetwork metered hint for a given ScanResult. |
| * |
| * If there is no ScoredNetwork associated with the ScanResult then false will be returned. |
| */ |
| public boolean getMeteredHint(ScanResult result) { |
| ScoredNetwork network = getScoredNetwork(result); |
| return network != null && network.meteredHint; |
| } |
| |
| public int getNetworkScore(ScanResult result, boolean isActiveNetwork) { |
| int score = INVALID_NETWORK_SCORE; |
| |
| ScoredNetwork network = getScoredNetwork(result); |
| if (network != null && network.rssiCurve != null) { |
| score = network.rssiCurve.lookupScore(result.level, isActiveNetwork); |
| if (DBG) { |
| Log.d(TAG, "getNetworkScore found scored network " + network.networkKey |
| + " score " + Integer.toString(score) |
| + " RSSI " + result.level |
| + " isActiveNetwork " + isActiveNetwork); |
| } |
| } |
| return score; |
| } |
| |
| @Nullable |
| public ScoredNetwork getScoredNetwork(ScanResult result) { |
| String key = buildNetworkKey(result); |
| if (key == null) return null; |
| |
| synchronized(mLock) { |
| ScoredNetwork network = mCache.get(key); |
| return network; |
| } |
| } |
| |
| /** Returns the ScoredNetwork for the given key. */ |
| @Nullable |
| public ScoredNetwork getScoredNetwork(NetworkKey networkKey) { |
| String key = buildNetworkKey(networkKey); |
| if (key == null) { |
| if (DBG) { |
| Log.d(TAG, "Could not build key string for Network Key: " + networkKey); |
| } |
| return null; |
| } |
| synchronized (mLock) { |
| return mCache.get(key); |
| } |
| } |
| |
| private String buildNetworkKey(ScoredNetwork network) { |
| if (network == null) { |
| return null; |
| } |
| return buildNetworkKey(network.networkKey); |
| } |
| |
| private String buildNetworkKey(NetworkKey networkKey) { |
| if (networkKey == null) { |
| return null; |
| } |
| if (networkKey.wifiKey == null) return null; |
| if (networkKey.type == NetworkKey.TYPE_WIFI) { |
| String key = networkKey.wifiKey.ssid; |
| if (key == null) return null; |
| if (networkKey.wifiKey.bssid != null) { |
| key = key + networkKey.wifiKey.bssid; |
| } |
| return key; |
| } |
| return null; |
| } |
| |
| private String buildNetworkKey(ScanResult result) { |
| if (result == null || result.SSID == null) { |
| return null; |
| } |
| StringBuilder key = new StringBuilder("\""); |
| key.append(result.SSID); |
| key.append("\""); |
| if (result.BSSID != null) { |
| key.append(result.BSSID); |
| } |
| return key.toString(); |
| } |
| |
| @Override protected final void dump(FileDescriptor fd, PrintWriter writer, String[] args) { |
| mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG); |
| String header = String.format("WifiNetworkScoreCache (%s/%d)", |
| mContext.getPackageName(), Process.myUid()); |
| writer.println(header); |
| writer.println(" All score curves:"); |
| synchronized (mLock) { |
| for (ScoredNetwork score : mCache.snapshot().values()) { |
| writer.println(" " + score); |
| } |
| writer.println(" Network scores for latest ScanResults:"); |
| WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); |
| for (ScanResult scanResult : wifiManager.getScanResults()) { |
| writer.println( |
| " " + buildNetworkKey(scanResult) + ": " + getNetworkScore(scanResult)); |
| } |
| } |
| } |
| |
| /** Registers a CacheListener instance, replacing the previous listener if it existed. */ |
| public void registerListener(CacheListener listener) { |
| synchronized (mLock) { |
| mListener = listener; |
| } |
| } |
| |
| /** Removes the registered CacheListener. */ |
| public void unregisterListener() { |
| synchronized (mLock) { |
| mListener = null; |
| } |
| } |
| |
| /** Listener for updates to the cache inside WifiNetworkScoreCache. */ |
| public abstract static class CacheListener { |
| private Handler mHandler; |
| |
| /** |
| * Constructor for CacheListener. |
| * |
| * @param handler the Handler on which to invoke the {@link #networkCacheUpdated} method. |
| * This cannot be null. |
| */ |
| public CacheListener(@NonNull Handler handler) { |
| Preconditions.checkNotNull(handler); |
| mHandler = handler; |
| } |
| |
| /** Invokes the {@link #networkCacheUpdated(List<ScoredNetwork>)} method on the handler. */ |
| void post(List<ScoredNetwork> updatedNetworks) { |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| networkCacheUpdated(updatedNetworks); |
| } |
| }); |
| } |
| |
| /** |
| * Invoked whenever the cache is updated. |
| * |
| * <p>Clearing the cache does not invoke this method. |
| * |
| * @param updatedNetworks the networks that were updated |
| */ |
| public abstract void networkCacheUpdated(List<ScoredNetwork> updatedNetworks); |
| } |
| } |