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 
     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 
     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      */
     76     public WifiNetworkScoreCache(Context context, @Nullable CacheListener listener) {
     77         mContext = context.getApplicationContext();
     78         mListener = listener;
     79         mNetworkCache = new HashMap<>();
     80     }
     81 
     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 
    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      */
    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      */
    135     public boolean hasScoreCurve(ScanResult result) {
    136         ScoredNetwork network = getScoredNetwork(result);
    137         return network != null && network.rssiCurve != null;
    138     }
    139 
    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      */
    161     public boolean getMeteredHint(ScanResult result) {
    162         ScoredNetwork network = getScoredNetwork(result);
    163         return network != null && network.meteredHint;
    164     }
    165 
    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
    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
    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 
    209     private String buildNetworkKey(ScoredNetwork network) {
    210         if (network == null) {
    211             return null;
    212         }
    213         return buildNetworkKey(network.networkKey);
    214     }
    215 
    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 
    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 
    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. */
    262     public void registerListener(CacheListener listener) {
    263         synchronized (mCacheLock) {
    264             mListener = listener;
    265         }
    266     }
    267 
    268     /** Removes the registered CacheListener. */
    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          */
    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. */
    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          */
    308         public abstract void networkCacheUpdated(List<ScoredNetwork> updatedNetworks);
    309     }
    310 }
    311