/*
 * 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;

INetworkScoreCache implementation for Wifi Networks.
@hide
/** * {@link INetworkScoreCache} implementation for Wifi Networks. * * @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 LruCache.
/** 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.
Params:
  • context – Application context
  • listener – CacheListener for cache updates
/** * 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.
/** * 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.
/** * 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.
/** * 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.
/** 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.
/** Registers a CacheListener instance, replacing the previous listener if it existed. */
public void registerListener(CacheListener listener) { synchronized (mLock) { mListener = listener; } }
Removes the registered CacheListener.
/** Removes the registered CacheListener. */
public void unregisterListener() { synchronized (mLock) { mListener = null; } }
Listener for updates to the cache inside WifiNetworkScoreCache.
/** Listener for updates to the cache inside WifiNetworkScoreCache. */
public abstract static class CacheListener { private Handler mHandler;
Constructor for CacheListener.
Params:
  • handler – the Handler on which to invoke the networkCacheUpdated method. This cannot be null.
/** * 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 #networkCacheUpdated(List<ScoredNetwork>) method on the 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.

Clearing the cache does not invoke this method.

Params:
  • updatedNetworks – the networks that were updated
/** * 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); } }