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