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