1 /*
2  * Copyright (C) 2019 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 com.android.wifitrackerlib;
18 
19 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
20 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
21 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
22 
23 import static java.util.stream.Collectors.toList;
24 
25 import android.content.BroadcastReceiver;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.net.ConnectivityManager;
30 import android.net.LinkProperties;
31 import android.net.Network;
32 import android.net.NetworkCapabilities;
33 import android.net.NetworkKey;
34 import android.net.NetworkRequest;
35 import android.net.NetworkScoreManager;
36 import android.net.ScoredNetwork;
37 import android.net.TransportInfo;
38 import android.net.vcn.VcnTransportInfo;
39 import android.net.wifi.WifiInfo;
40 import android.net.wifi.WifiManager;
41 import android.net.wifi.WifiNetworkScoreCache;
42 import android.os.Handler;
43 import android.os.Looper;
44 import android.telephony.SubscriptionManager;
45 import android.telephony.TelephonyManager;
46 import android.util.Log;
47 
48 import androidx.annotation.AnyThread;
49 import androidx.annotation.MainThread;
50 import androidx.annotation.NonNull;
51 import androidx.annotation.Nullable;
52 import androidx.annotation.WorkerThread;
53 import androidx.lifecycle.Lifecycle;
54 import androidx.lifecycle.LifecycleObserver;
55 import androidx.lifecycle.OnLifecycleEvent;
56 
57 import java.time.Clock;
58 import java.util.HashSet;
59 import java.util.List;
60 import java.util.Set;
61 
62 /**
63  * Base class for WifiTracker functionality.
64  *
65  * This class provides the basic functions of issuing scans, receiving Wi-Fi related broadcasts, and
66  * keeping track of the Wi-Fi state.
67  *
68  * Subclasses are expected to provide their own API to clients and override the empty broadcast
69  * handling methods here to populate the data returned by their API.
70  *
71  * This class runs on two threads:
72  *
73  * The main thread
74  * - Processes lifecycle events (onStart, onStop)
75  * - Runs listener callbacks
76  *
77  * The worker thread
78  * - Drives the periodic scan requests
79  * - Handles the system broadcasts to update the API return values
80  * - Notifies the listener for updates to the API return values
81  *
82  * To keep synchronization simple, this means that the vast majority of work is done within the
83  * worker thread. Synchronized blocks are only to be used for data returned by the API updated by
84  * the worker thread and consumed by the main thread.
85 */
86 
87 public class BaseWifiTracker implements LifecycleObserver {
88     private final String mTag;
89 
90     private static boolean sVerboseLogging;
91 
isVerboseLoggingEnabled()92     public static boolean isVerboseLoggingEnabled() {
93         return BaseWifiTracker.sVerboseLogging;
94     }
95 
96     private boolean mIsStarted;
97 
98     // Registered on the worker thread
99     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
100         @Override
101         @WorkerThread
102         public void onReceive(Context context, Intent intent) {
103             if (!mIsStarted) {
104                 mIsStarted = true;
105                 handleOnStart();
106             }
107 
108             String action = intent.getAction();
109 
110             if (isVerboseLoggingEnabled()) {
111                 Log.v(mTag, "Received broadcast: " + action);
112             }
113 
114             if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
115                 if (mWifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLED) {
116                     mScanner.start();
117                 } else {
118                     mScanner.stop();
119                 }
120                 notifyOnWifiStateChanged();
121                 handleWifiStateChangedAction();
122             } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
123                 mNetworkScoreManager.requestScores(mWifiManager.getScanResults().stream()
124                         .map(NetworkKey::createFromScanResult)
125                         .filter(mRequestedScoreKeys::add)
126                         .collect(toList()));
127                 handleScanResultsAvailableAction(intent);
128             } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action)) {
129                 handleConfiguredNetworksChangedAction(intent);
130             } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
131                 handleNetworkStateChangedAction(intent);
132             } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
133                 handleRssiChangedAction();
134             } else if (TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) {
135                 handleDefaultSubscriptionChanged(intent.getIntExtra(
136                         "subscription", SubscriptionManager.INVALID_SUBSCRIPTION_ID));
137             }
138         }
139     };
140     private final BaseWifiTracker.Scanner mScanner;
141     private final BaseWifiTrackerCallback mListener;
142 
143     protected final WifiTrackerInjector mInjector;
144     protected final Context mContext;
145     protected final WifiManager mWifiManager;
146     protected final ConnectivityManager mConnectivityManager;
147     protected final NetworkScoreManager mNetworkScoreManager;
148     protected final Handler mMainHandler;
149     protected final Handler mWorkerHandler;
150     protected final long mMaxScanAgeMillis;
151     protected final long mScanIntervalMillis;
152     protected final ScanResultUpdater mScanResultUpdater;
153     protected final WifiNetworkScoreCache mWifiNetworkScoreCache;
154     protected boolean mIsWifiValidated;
155     protected boolean mIsWifiDefaultRoute;
156     protected boolean mIsCellDefaultRoute;
157     private final Set<NetworkKey> mRequestedScoreKeys = new HashSet<>();
158 
159     // Network request for listening on changes to Wifi link properties and network capabilities
160     // such as captive portal availability.
161     private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
162             .clearCapabilities()
163             .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
164             .addTransportType(TRANSPORT_WIFI)
165             .build();
166 
167     private final ConnectivityManager.NetworkCallback mNetworkCallback =
168             new ConnectivityManager.NetworkCallback() {
169                 @Override
170                 @WorkerThread
171                 public void onLinkPropertiesChanged(@NonNull Network network,
172                         @NonNull LinkProperties lp) {
173                     if (!mIsStarted) {
174                         mIsStarted = true;
175                         handleOnStart();
176                     }
177                     if (!isPrimaryWifiNetwork(
178                             mConnectivityManager.getNetworkCapabilities(network))) {
179                         return;
180                     }
181                     handleLinkPropertiesChanged(lp);
182                 }
183 
184                 @Override
185                 @WorkerThread
186                 public void onCapabilitiesChanged(@NonNull Network network,
187                         @NonNull NetworkCapabilities networkCapabilities) {
188                     if (!mIsStarted) {
189                         mIsStarted = true;
190                         handleOnStart();
191                     }
192                     if (!isPrimaryWifiNetwork(networkCapabilities)) {
193                         return;
194                     }
195                     final boolean oldWifiValidated = mIsWifiValidated;
196                     mIsWifiValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED);
197                     if (isVerboseLoggingEnabled() && mIsWifiValidated != oldWifiValidated) {
198                         Log.v(mTag, "Is Wifi validated: " + mIsWifiValidated);
199                     }
200                     handleNetworkCapabilitiesChanged(networkCapabilities);
201                 }
202 
203                 @Override
204                 @WorkerThread
205                 public void onLost(@NonNull Network network) {
206                     if (!mIsStarted) {
207                         mIsStarted = true;
208                         handleOnStart();
209                     }
210                     if (!isPrimaryWifiNetwork(
211                             mConnectivityManager.getNetworkCapabilities(network))) {
212                         return;
213                     }
214                     mIsWifiValidated = false;
215                 }
216             };
217 
218     private final ConnectivityManager.NetworkCallback mDefaultNetworkCallback =
219             new ConnectivityManager.NetworkCallback() {
220                 @Override
221                 @WorkerThread
222                 public void onCapabilitiesChanged(@NonNull Network network,
223                         @NonNull NetworkCapabilities networkCapabilities) {
224                     if (!mIsStarted) {
225                         mIsStarted = true;
226                         handleOnStart();
227                     }
228                     final boolean oldWifiDefault = mIsWifiDefaultRoute;
229                     final boolean oldCellDefault = mIsCellDefaultRoute;
230                     TransportInfo transportInfo = networkCapabilities.getTransportInfo();
231                     final boolean isVcnOverWifi = transportInfo != null
232                             && transportInfo instanceof VcnTransportInfo
233                             && ((VcnTransportInfo) transportInfo).getWifiInfo() != null;
234                     // raw Wifi or VPN-over-Wifi or VCN-over-Wifi is default => Wifi is default.
235                     mIsWifiDefaultRoute = networkCapabilities.hasTransport(TRANSPORT_WIFI)
236                             || isVcnOverWifi;
237                     mIsCellDefaultRoute = !mIsWifiDefaultRoute
238                             && networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
239                     if (mIsWifiDefaultRoute != oldWifiDefault
240                             || mIsCellDefaultRoute != oldCellDefault) {
241                         if (isVerboseLoggingEnabled()) {
242                             Log.v(mTag, "Wifi is the default route: " + mIsWifiDefaultRoute);
243                             Log.v(mTag, "Cell is the default route: " + mIsCellDefaultRoute);
244                         }
245                         handleDefaultRouteChanged();
246                     }
247                 }
248 
249                 @WorkerThread
250                 public void onLost(@NonNull Network network) {
251                     if (!mIsStarted) {
252                         mIsStarted = true;
253                         handleOnStart();
254                     }
255                     mIsWifiDefaultRoute = false;
256                     mIsCellDefaultRoute = false;
257                     if (isVerboseLoggingEnabled()) {
258                         Log.v(mTag, "Wifi is the default route: false");
259                         Log.v(mTag, "Cell is the default route: false");
260                     }
261                     handleDefaultRouteChanged();
262                 }
263             };
264 
isPrimaryWifiNetwork(@ullable NetworkCapabilities networkCapabilities)265     private boolean isPrimaryWifiNetwork(@Nullable NetworkCapabilities networkCapabilities) {
266         if (networkCapabilities == null) {
267             return false;
268         }
269         final TransportInfo transportInfo = networkCapabilities.getTransportInfo();
270         if (!(transportInfo instanceof WifiInfo)) {
271             return false;
272         }
273         return ((WifiInfo) transportInfo).isPrimary();
274     }
275 
276     /**
277      * Constructor for BaseWifiTracker.
278      *
279      * @param wifiTrackerInjector injector for commonly referenced objects.
280      * @param lifecycle Lifecycle this is tied to for lifecycle callbacks.
281      * @param context Context for registering broadcast receiver and for resource strings.
282      * @param wifiManager Provides all Wi-Fi info.
283      * @param connectivityManager Provides network info.
284      * @param networkScoreManager Provides network scores for network badging.
285      * @param mainHandler Handler for processing listener callbacks.
286      * @param workerHandler Handler for processing all broadcasts and running the Scanner.
287      * @param clock Clock used for evaluating the age of scans
288      * @param maxScanAgeMillis Max age for tracked WifiEntries.
289      * @param scanIntervalMillis Interval between initiating scans.
290      */
BaseWifiTracker( @onNull WifiTrackerInjector injector, @NonNull Lifecycle lifecycle, @NonNull Context context, @NonNull WifiManager wifiManager, @NonNull ConnectivityManager connectivityManager, @NonNull NetworkScoreManager networkScoreManager, @NonNull Handler mainHandler, @NonNull Handler workerHandler, @NonNull Clock clock, long maxScanAgeMillis, long scanIntervalMillis, BaseWifiTrackerCallback listener, String tag)291     BaseWifiTracker(
292             @NonNull WifiTrackerInjector injector,
293             @NonNull Lifecycle lifecycle, @NonNull Context context,
294             @NonNull WifiManager wifiManager,
295             @NonNull ConnectivityManager connectivityManager,
296             @NonNull NetworkScoreManager networkScoreManager,
297             @NonNull Handler mainHandler,
298             @NonNull Handler workerHandler,
299             @NonNull Clock clock,
300             long maxScanAgeMillis,
301             long scanIntervalMillis,
302             BaseWifiTrackerCallback listener,
303             String tag) {
304         mInjector = injector;
305         lifecycle.addObserver(this);
306         mContext = context;
307         mWifiManager = wifiManager;
308         mConnectivityManager = connectivityManager;
309         mNetworkScoreManager = networkScoreManager;
310         mMainHandler = mainHandler;
311         mWorkerHandler = workerHandler;
312         mMaxScanAgeMillis = maxScanAgeMillis;
313         mScanIntervalMillis = scanIntervalMillis;
314         mListener = listener;
315         mTag = tag;
316 
317         mScanResultUpdater = new ScanResultUpdater(clock,
318                 maxScanAgeMillis + scanIntervalMillis);
319         mWifiNetworkScoreCache = new WifiNetworkScoreCache(mContext,
320                 new WifiNetworkScoreCache.CacheListener(mWorkerHandler) {
321                     @Override
322                     public void networkCacheUpdated(List<ScoredNetwork> networks) {
323                         handleNetworkScoreCacheUpdated();
324                     }
325                 });
326         mScanner = new BaseWifiTracker.Scanner(workerHandler.getLooper());
327         sVerboseLogging = mWifiManager.isVerboseLoggingEnabled();
328     }
329 
330     /**
331      * Registers the broadcast receiver and network callbacks and starts the scanning mechanism.
332      */
333     @OnLifecycleEvent(Lifecycle.Event.ON_START)
334     @MainThread
onStart()335     public void onStart() {
336         IntentFilter filter = new IntentFilter();
337         filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
338         filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
339         filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
340         filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
341         filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
342         filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
343         mContext.registerReceiver(mBroadcastReceiver, filter,
344                 /* broadcastPermission */ null, mWorkerHandler);
345         mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback,
346                 mWorkerHandler);
347         mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback,
348                 mWorkerHandler);
349         final NetworkCapabilities defaultNetworkCapabilities = mConnectivityManager
350                 .getNetworkCapabilities(mConnectivityManager.getActiveNetwork());
351         if (defaultNetworkCapabilities != null) {
352             mIsWifiDefaultRoute = defaultNetworkCapabilities.hasTransport(TRANSPORT_WIFI);
353             mIsCellDefaultRoute = defaultNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR);
354         } else {
355             mIsWifiDefaultRoute = false;
356             mIsCellDefaultRoute = false;
357         }
358         if (isVerboseLoggingEnabled()) {
359             Log.v(mTag, "Wifi is the default route: " + mIsWifiDefaultRoute);
360             Log.v(mTag, "Cell is the default route: " + mIsCellDefaultRoute);
361         }
362 
363         mNetworkScoreManager.registerNetworkScoreCache(
364                 NetworkKey.TYPE_WIFI,
365                 mWifiNetworkScoreCache,
366                 NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS);
367         mWorkerHandler.post(() -> {
368             if (!mIsStarted) {
369                 mIsStarted = true;
370                 handleOnStart();
371             }
372         });
373     }
374 
375     /**
376      * Unregisters the broadcast receiver, network callbacks, and pauses the scanning mechanism.
377      */
378     @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
379     @MainThread
onStop()380     public void onStop() {
381         mWorkerHandler.post(mScanner::stop);
382         mContext.unregisterReceiver(mBroadcastReceiver);
383         mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
384         mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback);
385         mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI,
386                 mWifiNetworkScoreCache);
387         mWorkerHandler.post(mRequestedScoreKeys::clear);
388         mIsStarted = false;
389     }
390 
391     /**
392      * Returns the state of Wi-Fi as one of the following values.
393      *
394      * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li>
395      * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li>
396      * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li>
397      * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li>
398      * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li>
399      */
400     @AnyThread
getWifiState()401     public int getWifiState() {
402         return mWifiManager.getWifiState();
403     }
404 
405     /**
406      * Method to run on the worker thread when onStart is invoked.
407      * Data that can be updated immediately after onStart should be populated here.
408      */
409     @WorkerThread
handleOnStart()410     protected  void handleOnStart() {
411         // Do nothing.
412     }
413 
414     /**
415      * Handle receiving the WifiManager.WIFI_STATE_CHANGED_ACTION broadcast
416      */
417     @WorkerThread
handleWifiStateChangedAction()418     protected void handleWifiStateChangedAction() {
419         // Do nothing.
420     }
421 
422     /**
423      * Handle receiving the WifiManager.SCAN_RESULTS_AVAILABLE_ACTION broadcast
424      */
425     @WorkerThread
handleScanResultsAvailableAction(@onNull Intent intent)426     protected void handleScanResultsAvailableAction(@NonNull Intent intent) {
427         // Do nothing.
428     }
429 
430     /**
431      * Handle receiving the WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION broadcast
432      */
433     @WorkerThread
handleConfiguredNetworksChangedAction(@onNull Intent intent)434     protected void handleConfiguredNetworksChangedAction(@NonNull Intent intent) {
435         // Do nothing.
436     }
437 
438     /**
439      * Handle receiving the WifiManager.NETWORK_STATE_CHANGED_ACTION broadcast
440      */
441     @WorkerThread
handleNetworkStateChangedAction(@onNull Intent intent)442     protected void handleNetworkStateChangedAction(@NonNull Intent intent) {
443         // Do nothing.
444     }
445 
446     /**
447      * Handle receiving the WifiManager.RSSI_CHANGED_ACTION broadcast
448      */
449     @WorkerThread
handleRssiChangedAction()450     protected void handleRssiChangedAction() {
451         // Do nothing.
452     }
453 
454     /**
455      * Handle link property changes for the current connected Wifi network.
456      */
457     @WorkerThread
handleLinkPropertiesChanged(@ullable LinkProperties linkProperties)458     protected void handleLinkPropertiesChanged(@Nullable LinkProperties linkProperties) {
459         // Do nothing.
460     }
461 
462     /**
463      * Handle network capability changes for the current connected Wifi network.
464      */
465     @WorkerThread
handleNetworkCapabilitiesChanged(@ullable NetworkCapabilities capabilities)466     protected void handleNetworkCapabilitiesChanged(@Nullable NetworkCapabilities capabilities) {
467         // Do nothing.
468     }
469 
470     /**
471      * Handle when the default route changes. Whether Wifi is the default route is stored in
472      * mIsWifiDefaultRoute.
473      */
474     @WorkerThread
handleDefaultRouteChanged()475     protected void handleDefaultRouteChanged() {
476         // Do nothing.
477     }
478 
479     /**
480      * Handle updates to the Wifi network score cache, which is stored in mWifiNetworkScoreCache
481      */
482     @WorkerThread
handleNetworkScoreCacheUpdated()483     protected void handleNetworkScoreCacheUpdated() {
484         // Do nothing.
485     }
486 
487     /**
488      * Handle updates to the default data subscription id from SubscriptionManager.
489      */
490     @WorkerThread
handleDefaultSubscriptionChanged(int defaultSubId)491     protected void handleDefaultSubscriptionChanged(int defaultSubId) {
492         // Do nothing.
493     }
494 
495     /**
496      * Scanner to handle starting scans every SCAN_INTERVAL_MILLIS
497      */
498     @WorkerThread
499     private class Scanner extends Handler {
500         private static final int SCAN_RETRY_TIMES = 3;
501 
502         private int mRetry = 0;
503         private boolean mIsActive;
504 
Scanner(Looper looper)505         private Scanner(Looper looper) {
506             super(looper);
507         }
508 
start()509         private void start() {
510             if (!mIsActive) {
511                 mIsActive = true;
512                 if (isVerboseLoggingEnabled()) {
513                     Log.v(mTag, "Scanner start");
514                 }
515                 postScan();
516             }
517         }
518 
stop()519         private void stop() {
520             mIsActive = false;
521             if (isVerboseLoggingEnabled()) {
522                 Log.v(mTag, "Scanner stop");
523             }
524             mRetry = 0;
525             removeCallbacksAndMessages(null);
526         }
527 
postScan()528         private void postScan() {
529             if (mWifiManager.startScan()) {
530                 mRetry = 0;
531             } else if (++mRetry >= SCAN_RETRY_TIMES) {
532                 // TODO(b/70983952): See if toast is needed here
533                 if (isVerboseLoggingEnabled()) {
534                     Log.v(mTag, "Scanner failed to start scan " + mRetry + " times!");
535                 }
536                 mRetry = 0;
537                 return;
538             }
539             postDelayed(this::postScan, mScanIntervalMillis);
540         }
541     }
542 
543     /**
544      * Posts onWifiStateChanged callback on the main thread.
545      */
546     @WorkerThread
notifyOnWifiStateChanged()547     private void notifyOnWifiStateChanged() {
548         if (mListener != null) {
549             mMainHandler.post(mListener::onWifiStateChanged);
550         }
551     }
552 
553     /**
554      * Base callback handling Wi-Fi state changes
555      *
556      * Subclasses should extend this for their own needs.
557      */
558     protected interface BaseWifiTrackerCallback {
559         /**
560          * Called when the value for {@link #getWifiState() has changed.
561          */
562         @MainThread
onWifiStateChanged()563         void onWifiStateChanged();
564     }
565 }
566