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.TRANSPORT_WIFI;
20 import static android.os.Build.VERSION_CODES;
21 
22 import android.annotation.TargetApi;
23 import android.app.ActivityManager;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.net.ConnectivityDiagnosticsManager;
29 import android.net.ConnectivityManager;
30 import android.net.LinkProperties;
31 import android.net.Network;
32 import android.net.NetworkCapabilities;
33 import android.net.NetworkRequest;
34 import android.net.wifi.ScanResult;
35 import android.net.wifi.WifiManager;
36 import android.net.wifi.WifiScanner;
37 import android.net.wifi.sharedconnectivity.app.HotspotNetwork;
38 import android.net.wifi.sharedconnectivity.app.HotspotNetworkConnectionStatus;
39 import android.net.wifi.sharedconnectivity.app.KnownNetwork;
40 import android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus;
41 import android.net.wifi.sharedconnectivity.app.SharedConnectivityClientCallback;
42 import android.net.wifi.sharedconnectivity.app.SharedConnectivityManager;
43 import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState;
44 import android.os.Handler;
45 import android.os.Looper;
46 import android.telephony.SubscriptionManager;
47 import android.telephony.TelephonyManager;
48 import android.util.Log;
49 
50 import androidx.annotation.AnyThread;
51 import androidx.annotation.MainThread;
52 import androidx.annotation.NonNull;
53 import androidx.annotation.Nullable;
54 import androidx.annotation.WorkerThread;
55 import androidx.core.os.BuildCompat;
56 import androidx.lifecycle.Lifecycle;
57 import androidx.lifecycle.LifecycleObserver;
58 import androidx.lifecycle.OnLifecycleEvent;
59 
60 import java.time.Clock;
61 import java.util.ArrayList;
62 import java.util.List;
63 import java.util.concurrent.Executor;
64 
65 /**
66  * Base class for WifiTracker functionality.
67  *
68  * This class provides the basic functions of issuing scans, receiving Wi-Fi related broadcasts, and
69  * keeping track of the Wi-Fi state.
70  *
71  * Subclasses are expected to provide their own API to clients and override the empty broadcast
72  * handling methods here to populate the data returned by their API.
73  *
74  * This class runs on two threads:
75  *
76  * The main thread
77  * - Processes lifecycle events (onStart, onStop)
78  * - Runs listener callbacks
79  *
80  * The worker thread
81  * - Drives the periodic scan requests
82  * - Handles the system broadcasts to update the API return values
83  * - Notifies the listener for updates to the API return values
84  *
85  * To keep synchronization simple, this means that the vast majority of work is done within the
86  * worker thread. Synchronized blocks are only to be used for data returned by the API updated by
87  * the worker thread and consumed by the main thread.
88 */
89 
90 public class BaseWifiTracker {
91     private final String mTag;
92 
isVerboseLoggingEnabled()93     public boolean isVerboseLoggingEnabled() {
94         return mInjector.isVerboseLoggingEnabled();
95     }
96 
97     private int mWifiState = WifiManager.WIFI_STATE_DISABLED;
98 
99     private boolean mIsInitialized = false;
100     private boolean mIsScanningDisabled = false;
101 
102     class WifiTrackerLifecycleObserver implements LifecycleObserver {
103         @OnLifecycleEvent(Lifecycle.Event.ON_START)
104         @MainThread
onStart()105         public void onStart() {
106             BaseWifiTracker.this.onStart();
107         }
108 
109         @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
110         @MainThread
onStop()111         public void onStop() {
112             BaseWifiTracker.this.onStop();
113         }
114 
115         @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
116         @MainThread
onDestroy()117         public void onDestroy() {
118             BaseWifiTracker.this.onDestroy();
119         }
120     };
121 
122     // Registered on the worker thread
123     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
124         @Override
125         @WorkerThread
126         public void onReceive(Context context, Intent intent) {
127             String action = intent.getAction();
128 
129             if (isVerboseLoggingEnabled()) {
130                 Log.v(mTag, "Received broadcast: " + action);
131             }
132 
133             if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
134                 mWifiState = intent.getIntExtra(
135                         WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_DISABLED);
136                 mScanner.onWifiStateChanged(mWifiState == WifiManager.WIFI_STATE_ENABLED);
137                 notifyOnWifiStateChanged();
138                 handleWifiStateChangedAction();
139             } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
140                 handleScanResultsAvailableAction(intent);
141             } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action)) {
142                 handleConfiguredNetworksChangedAction(intent);
143             } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
144                 handleNetworkStateChangedAction(intent);
145             } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
146                 handleRssiChangedAction(intent);
147             } else if (TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) {
148                 handleDefaultSubscriptionChanged(intent.getIntExtra(
149                         "subscription", SubscriptionManager.INVALID_SUBSCRIPTION_ID));
150             }
151         }
152     };
153     private final BaseWifiTracker.Scanner mScanner;
154     private final BaseWifiTrackerCallback mListener;
155     private final @NonNull LifecycleObserver mLifecycleObserver =
156             new WifiTrackerLifecycleObserver();
157 
158     protected final WifiTrackerInjector mInjector;
159     protected final Context mContext;
160     protected final @NonNull ActivityManager mActivityManager;
161     protected final WifiManager mWifiManager;
162     protected final ConnectivityManager mConnectivityManager;
163     protected final ConnectivityDiagnosticsManager mConnectivityDiagnosticsManager;
164     protected final Handler mMainHandler;
165     protected final Handler mWorkerHandler;
166     protected final long mMaxScanAgeMillis;
167     protected final long mScanIntervalMillis;
168     protected final ScanResultUpdater mScanResultUpdater;
169 
170     @Nullable protected SharedConnectivityManager mSharedConnectivityManager = null;
171 
172     // Network request for listening on changes to Wifi link properties and network capabilities
173     // such as captive portal availability.
174     private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
175             .clearCapabilities()
176             .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
177             .addTransportType(TRANSPORT_WIFI)
178             .build();
179 
180     private final ConnectivityManager.NetworkCallback mNetworkCallback =
181             new ConnectivityManager.NetworkCallback(
182                     ConnectivityManager.NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) {
183                 @Override
184                 @WorkerThread
185                 public void onLinkPropertiesChanged(@NonNull Network network,
186                         @NonNull LinkProperties lp) {
187                     handleLinkPropertiesChanged(network, lp);
188                 }
189 
190                 @Override
191                 @WorkerThread
192                 public void onCapabilitiesChanged(@NonNull Network network,
193                         @NonNull NetworkCapabilities networkCapabilities) {
194                     handleNetworkCapabilitiesChanged(network, networkCapabilities);
195                 }
196 
197                 @Override
198                 @WorkerThread
199                 public void onLost(@NonNull Network network) {
200                     handleNetworkLost(network);
201                 }
202             };
203 
204     private final ConnectivityManager.NetworkCallback mDefaultNetworkCallback =
205             new ConnectivityManager.NetworkCallback(
206                     ConnectivityManager.NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) {
207                 @Override
208                 @WorkerThread
209                 public void onCapabilitiesChanged(@NonNull Network network,
210                         @NonNull NetworkCapabilities networkCapabilities) {
211                     handleDefaultNetworkCapabilitiesChanged(network, networkCapabilities);
212                 }
213 
214                 @WorkerThread
215                 public void onLost(@NonNull Network network) {
216                     handleDefaultNetworkLost();
217                 }
218             };
219 
220     private final ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback
221             mConnectivityDiagnosticsCallback =
222             new ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback() {
223         @Override
224         public void onConnectivityReportAvailable(
225                 @NonNull ConnectivityDiagnosticsManager.ConnectivityReport report) {
226             handleConnectivityReportAvailable(report);
227         }
228     };
229 
230     private final Executor mConnectivityDiagnosticsExecutor = new Executor() {
231         @Override
232         public void execute(Runnable command) {
233             mWorkerHandler.post(command);
234         }
235     };
236 
237     @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE)
238     private final Executor mSharedConnectivityExecutor = new Executor() {
239         @Override
240         public void execute(Runnable command) {
241             mWorkerHandler.post(command);
242         }
243     };
244 
245     @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE)
246     @Nullable
247     private SharedConnectivityClientCallback mSharedConnectivityCallback = null;
248 
249     @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE)
250     @NonNull
createSharedConnectivityCallback()251     private SharedConnectivityClientCallback createSharedConnectivityCallback() {
252         return new SharedConnectivityClientCallback() {
253             @Override
254             public void onHotspotNetworksUpdated(@NonNull List<HotspotNetwork> networks) {
255                 handleHotspotNetworksUpdated(networks);
256             }
257 
258             @Override
259             public void onKnownNetworksUpdated(@NonNull List<KnownNetwork> networks) {
260                 handleKnownNetworksUpdated(networks);
261             }
262 
263             @Override
264             public void onSharedConnectivitySettingsChanged(
265                     @NonNull SharedConnectivitySettingsState state) {
266                 handleSharedConnectivitySettingsChanged(state);
267             }
268 
269             @Override
270             public void onHotspotNetworkConnectionStatusChanged(
271                     @NonNull HotspotNetworkConnectionStatus status) {
272                 handleHotspotNetworkConnectionStatusChanged(status);
273             }
274 
275             @Override
276             public void onKnownNetworkConnectionStatusChanged(
277                     @NonNull KnownNetworkConnectionStatus status) {
278                 handleKnownNetworkConnectionStatusChanged(status);
279             }
280 
281             @Override
282             public void onServiceConnected() {
283                 handleServiceConnected();
284             }
285 
286             @Override
287             public void onServiceDisconnected() {
288                 handleServiceDisconnected();
289             }
290 
291             @Override
292             public void onRegisterCallbackFailed(Exception exception) {
293                 handleRegisterCallbackFailed(exception);
294             }
295         };
296     }
297 
298     /**
299      * Constructor for BaseWifiTracker.
300      * @param injector Injector for commonly referenced objects.
301      * @param lifecycle Lifecycle to register the internal LifecycleObserver with. Note that we
302      *                  register the LifecycleObserver inside the constructor, which may cause an
303      *                  NPE if the Lifecycle invokes onStart/onStop/onDestroyed within
304      *                  {@link Lifecycle#addObserver}. To avoid this, pass {@code null} here and
305      *                  register the LifecycleObserver from {@link #getLifecycleObserver()}
306      *                  instead.
307      * @param context Context for registering broadcast receiver and for resource strings.
308      * @param wifiManager Provides all Wi-Fi info.
309      * @param connectivityManager Provides network info.
310      * @param mainHandler Handler for processing listener callbacks.
311      * @param workerHandler Handler for processing all broadcasts and running the Scanner.
312      * @param clock Clock used for evaluating the age of scans
313      * @param maxScanAgeMillis Max age for tracked WifiEntries.
314      * @param scanIntervalMillis Interval between initiating scans.
315      */
316     @SuppressWarnings("StaticAssignmentInConstructor")
317     BaseWifiTracker(
318             @NonNull WifiTrackerInjector injector,
319             @Nullable Lifecycle lifecycle, @NonNull Context context,
320             @NonNull WifiManager wifiManager,
321             @NonNull ConnectivityManager connectivityManager,
322             @NonNull Handler mainHandler,
323             @NonNull Handler workerHandler,
324             @NonNull Clock clock,
325             long maxScanAgeMillis,
326             long scanIntervalMillis,
327             BaseWifiTrackerCallback listener,
328             String tag) {
329         mInjector = injector;
330         mActivityManager = context.getSystemService(ActivityManager.class);
331         mContext = context;
332         mWifiManager = wifiManager;
333         mConnectivityManager = connectivityManager;
334         mConnectivityDiagnosticsManager =
335                 context.getSystemService(ConnectivityDiagnosticsManager.class);
336         if (mInjector.isSharedConnectivityFeatureEnabled() && BuildCompat.isAtLeastU()) {
337             mSharedConnectivityManager = context.getSystemService(SharedConnectivityManager.class);
338             mSharedConnectivityCallback = createSharedConnectivityCallback();
339         }
340         mMainHandler = mainHandler;
341         mWorkerHandler = workerHandler;
342         mMaxScanAgeMillis = maxScanAgeMillis;
343         mScanIntervalMillis = scanIntervalMillis;
344         mListener = listener;
345         mTag = tag;
346 
347         mScanResultUpdater = new ScanResultUpdater(clock,
348                 maxScanAgeMillis + scanIntervalMillis);
349         mScanner = new BaseWifiTracker.Scanner(workerHandler.getLooper());
350 
351         if (lifecycle != null) { // Need to add after constructor completes.
352             mMainHandler.post(() -> lifecycle.addObserver(mLifecycleObserver));
353         }
354     }
355 
356     /**
357      * Disable the scanning mechanism permanently.
358      */
359     public void disableScanning() {
360         mIsScanningDisabled = true;
361         // This method indicates SystemUI usage, which shouldn't output verbose logs since it's
362         // always up.
363         mInjector.disableVerboseLogging();
364     }
365 
366     /**
367      * Returns the LifecycleObserver to listen on the app's lifecycle state.
368      */
369     @AnyThread
370     public LifecycleObserver getLifecycleObserver() {
371         return mLifecycleObserver;
372     }
373 
374     /**
375      * Registers the broadcast receiver and network callbacks and starts the scanning mechanism.
376      */
377     @MainThread
378     public void onStart() {
379         if (isVerboseLoggingEnabled()) {
380             Log.v(mTag, "onStart");
381         }
382         mScanner.onStart();
383         mWorkerHandler.post(() -> {
384             IntentFilter filter = new IntentFilter();
385             filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
386             if (!mIsScanningDisabled) {
387                 filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
388             }
389             filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
390             filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
391             if (isVerboseLoggingEnabled()) {
392                 filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
393             }
394             filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
395             filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
396             mContext.registerReceiver(mBroadcastReceiver, filter,
397                     /* broadcastPermission */ null, mWorkerHandler);
398             mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback,
399                     mWorkerHandler);
400             mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback,
401                     mWorkerHandler);
402             mConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback(mNetworkRequest,
403                     mConnectivityDiagnosticsExecutor, mConnectivityDiagnosticsCallback);
404             if (mSharedConnectivityManager != null && mSharedConnectivityCallback != null
405                     && BuildCompat.isAtLeastU()) {
406                 mSharedConnectivityManager.registerCallback(mSharedConnectivityExecutor,
407                         mSharedConnectivityCallback);
408             }
409             handleOnStart();
410             mIsInitialized = true;
411         });
412     }
413 
414     /**
415      * Unregisters the broadcast receiver, network callbacks, and pauses the scanning mechanism.
416      */
417     @MainThread
418     public void onStop() {
419         if (isVerboseLoggingEnabled()) {
420             Log.v(mTag, "onStop");
421         }
422         mScanner.onStop();
423         mWorkerHandler.post(() -> {
424             try {
425                 mContext.unregisterReceiver(mBroadcastReceiver);
426                 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
427                 mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback);
428                 mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback(
429                         mConnectivityDiagnosticsCallback);
430                 if (mSharedConnectivityManager != null && mSharedConnectivityCallback != null
431                         && BuildCompat.isAtLeastU()) {
432                     boolean result =
433                             mSharedConnectivityManager.unregisterCallback(
434                                     mSharedConnectivityCallback);
435                     if (!result) {
436                         Log.e(mTag, "onStop: unregisterCallback failed");
437                     }
438                 }
439             } catch (IllegalArgumentException e) {
440                 // Already unregistered in onDestroyed().
441             }
442         });
443     }
444 
445     /**
446      * Unregisters the broadcast receiver network callbacks in case the Activity is destroyed before
447      * the worker thread runnable posted in onStop() runs.
448      */
449     @MainThread
450     public void onDestroy() {
451         try {
452             mContext.unregisterReceiver(mBroadcastReceiver);
453             mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
454             mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback);
455             mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback(
456                     mConnectivityDiagnosticsCallback);
457             if (mSharedConnectivityManager != null && mSharedConnectivityCallback != null
458                     && BuildCompat.isAtLeastU()) {
459                 boolean result =
460                         mSharedConnectivityManager.unregisterCallback(
461                                 mSharedConnectivityCallback);
462                 if (!result) {
463                     Log.e(mTag, "onDestroyed: unregisterCallback failed");
464                 }
465             }
466         } catch (IllegalArgumentException e) {
467             // Already unregistered in onStop() worker thread runnable.
468         }
469     }
470 
471     /**
472      * Returns true if this WifiTracker has already been initialized in the worker thread via
473      * handleOnStart()
474      */
475     @AnyThread
476     boolean isInitialized() {
477         return mIsInitialized;
478     }
479 
480     /**
481      * Returns the state of Wi-Fi as one of the following values.
482      *
483      * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li>
484      * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li>
485      * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li>
486      * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li>
487      * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li>
488      */
489     @AnyThread
490     public int getWifiState() {
491         return mWifiState;
492     }
493 
494     /**
495      * Method to run on the worker thread when onStart is invoked.
496      * Data that can be updated immediately after onStart should be populated here.
497      */
498     @WorkerThread
499     protected  void handleOnStart() {
500         // Do nothing.
501     }
502 
503     /**
504      * Handle receiving the WifiManager.WIFI_STATE_CHANGED_ACTION broadcast
505      */
506     @WorkerThread
507     protected void handleWifiStateChangedAction() {
508         // Do nothing.
509     }
510 
511     /**
512      * Handle receiving the WifiManager.SCAN_RESULTS_AVAILABLE_ACTION broadcast
513      */
514     @WorkerThread
515     protected void handleScanResultsAvailableAction(@NonNull Intent intent) {
516         // Do nothing.
517     }
518 
519     /**
520      * Handle receiving the WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION broadcast
521      */
522     @WorkerThread
523     protected void handleConfiguredNetworksChangedAction(@NonNull Intent intent) {
524         // Do nothing.
525     }
526 
527     /**
528      * Handle receiving the WifiManager.NETWORK_STATE_CHANGED_ACTION broadcast
529      */
530     @WorkerThread
531     protected void handleNetworkStateChangedAction(@NonNull Intent intent) {
532         // Do nothing.
533     }
534 
535     /**
536      * Handle receiving the WifiManager.NETWORK_STATE_CHANGED_ACTION broadcast
537      */
538     @WorkerThread
539     protected void handleRssiChangedAction(@NonNull Intent intent) {
540         // Do nothing.
541     }
542 
543     /**
544      * Handle link property changes for the given network.
545      */
546     @WorkerThread
547     protected void handleLinkPropertiesChanged(
548             @NonNull Network network, @Nullable LinkProperties linkProperties) {
549         // Do nothing.
550     }
551 
552     /**
553      * Handle network capability changes for the current connected Wifi network.
554      */
555     @WorkerThread
556     protected void handleNetworkCapabilitiesChanged(
557             @NonNull Network network, @NonNull NetworkCapabilities capabilities) {
558         // Do nothing.
559     }
560 
561     /**
562      * Handle the loss of a network.
563      */
564     @WorkerThread
565     protected void handleNetworkLost(@NonNull Network network) {
566         // Do nothing.
567     }
568 
569     /**
570      * Handle receiving a connectivity report.
571      */
572     @WorkerThread
573     protected void handleConnectivityReportAvailable(
574             @NonNull ConnectivityDiagnosticsManager.ConnectivityReport connectivityReport) {
575         // Do nothing.
576     }
577 
578     /**
579      * Handle default network capabilities changed.
580      */
581     @WorkerThread
582     protected void handleDefaultNetworkCapabilitiesChanged(@NonNull Network network,
583             @NonNull NetworkCapabilities networkCapabilities) {
584         // Do nothing.
585     }
586 
587     /**
588      * Handle default network loss.
589      */
590     @WorkerThread
591     protected void handleDefaultNetworkLost() {
592         // Do nothing.
593     }
594 
595     /**
596      * Handle updates to the default data subscription id from SubscriptionManager.
597      */
598     @WorkerThread
599     protected void handleDefaultSubscriptionChanged(int defaultSubId) {
600         // Do nothing.
601     }
602 
603     /**
604      * Handle updates to the list of tether networks from SharedConnectivityManager.
605      */
606     @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE)
607     @WorkerThread
608     protected void handleHotspotNetworksUpdated(List<HotspotNetwork> networks) {
609         // Do nothing.
610     }
611 
612     /**
613      * Handle updates to the list of known networks from SharedConnectivityManager.
614      */
615     @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE)
616     @WorkerThread
617     protected void handleKnownNetworksUpdated(List<KnownNetwork> networks) {
618         // Do nothing.
619     }
620 
621     /**
622      * Handle changes to the shared connectivity settings from SharedConnectivityManager.
623      */
624     @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE)
625     @WorkerThread
626     protected void handleSharedConnectivitySettingsChanged(
627             @NonNull SharedConnectivitySettingsState state) {
628         // Do nothing.
629     }
630 
631     /**
632      * Handle changes to the shared connectivity settings from SharedConnectivityManager.
633      */
634     @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE)
635     @WorkerThread
636     protected void handleHotspotNetworkConnectionStatusChanged(
637             @NonNull HotspotNetworkConnectionStatus status) {
638         // Do nothing.
639     }
640 
641     /**
642      * Handle changes to the shared connectivity settings from SharedConnectivityManager.
643      */
644     @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE)
645     @WorkerThread
646     protected void handleKnownNetworkConnectionStatusChanged(
647             @NonNull KnownNetworkConnectionStatus status) {
648         // Do nothing.
649     }
650 
651     /**
652      * Handle service connected callback from SharedConnectivityManager.
653      */
654     @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE)
655     @WorkerThread
656     protected void handleServiceConnected() {
657         // Do nothing.
658     }
659 
660     /**
661      * Handle service disconnected callback from SharedConnectivityManager.
662      */
663     @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE)
664     @WorkerThread
665     protected void handleServiceDisconnected() {
666         // Do nothing.
667     }
668 
669     /**
670      * Handle register callback failed callback from SharedConnectivityManager.
671      */
672     @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE)
673     @WorkerThread
674     protected void handleRegisterCallbackFailed(Exception exception) {
675         // Do nothing.
676     }
677 
678     /**
679      * Helper class to handle starting scans every SCAN_INTERVAL_MILLIS.
680      *
681      * Scanning is only done when the activity is in the Started state and Wi-Fi is enabled.
682      */
683     private class Scanner extends Handler {
684         private boolean mIsStartedState = false;
685         private boolean mIsWifiEnabled = false;
686         private final WifiScanner.ScanListener mFirstScanListener = new WifiScanner.ScanListener() {
687             @Override
688             @MainThread
689             public void onPeriodChanged(int periodInMs) {
690                 // No-op.
691             }
692 
693             @Override
694             @MainThread
695             public void onResults(WifiScanner.ScanData[] results) {
696                 mWorkerHandler.post(() -> {
697                     if (!shouldScan()) {
698                         return;
699                     }
700                     if (isVerboseLoggingEnabled()) {
701                         Log.v(mTag, "Received scan results from first scan request.");
702                     }
703                     List<ScanResult> scanResults = new ArrayList<>();
704                     if (results != null) {
705                         for (WifiScanner.ScanData scanData : results) {
706                             scanResults.addAll(List.of(scanData.getResults()));
707                         }
708                     }
709                     // Fake a SCAN_RESULTS_AVAILABLE_ACTION. The results should already be populated
710                     // in mScanResultUpdater, which is the source of truth for the child classes.
711                     mScanResultUpdater.update(scanResults);
712                     handleScanResultsAvailableAction(
713                             new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
714                                     .putExtra(WifiManager.EXTRA_RESULTS_UPDATED, true));
715                     // Now start scanning via WifiManager.startScan().
716                     scanLoop();
717                 });
718             }
719 
720             @Override
721             @MainThread
722             public void onFullResult(ScanResult fullScanResult) {
723                 // No-op.
724             }
725 
726             @Override
727             @MainThread
728             public void onSuccess() {
729                 // No-op.
730             }
731 
732             @Override
733             @MainThread
734             public void onFailure(int reason, String description) {
735                 mWorkerHandler.post(() -> {
736                     if (!mIsWifiEnabled) {
737                         return;
738                     }
739                     Log.e(mTag, "Failed to scan! Reason: " + reason + ", ");
740                     // First scan failed, start scanning normally anyway.
741                     scanLoop();
742                 });
743             }
744         };
745 
746         private Scanner(Looper looper) {
747             super(looper);
748         }
749 
750         /**
751          * Called when the activity enters the Started state.
752          * When this happens, evaluate if we need to start scanning.
753          */
754         @MainThread
755         private void onStart() {
756             mIsStartedState = true;
757             mWorkerHandler.post(this::possiblyStartScanning);
758         }
759 
760         /**
761          * Called when the activity exits the Started state.
762          * When this happens, stop scanning.
763          */
764         @MainThread
765         private void onStop() {
766             mIsStartedState = false;
767             mWorkerHandler.post(this::stopScanning);
768         }
769 
770         /**
771          * Called whenever the Wi-Fi state changes. If the new state differs from the old state,
772          * then re-evaluate whether we need to start or stop scanning.
773          * @param enabled Whether Wi-Fi is enabled or not.
774          */
775         @WorkerThread
776         private void onWifiStateChanged(boolean enabled) {
777             boolean oldEnabled = mIsWifiEnabled;
778             mIsWifiEnabled = enabled;
779             if (mIsWifiEnabled != oldEnabled) {
780                 if (mIsWifiEnabled) {
781                     possiblyStartScanning();
782                 } else {
783                     stopScanning();
784                 }
785             }
786         }
787 
788         /**
789          * Returns true if we should be scanning and false if not.
790          * Scanning should only happen when Wi-Fi is enabled and the activity is started.
791          */
792         private boolean shouldScan() {
793             return mIsWifiEnabled && mIsStartedState && !mIsScanningDisabled;
794         }
795 
796         @WorkerThread
797         private void possiblyStartScanning() {
798             if (!shouldScan()) {
799                 return;
800             }
801             Log.i(mTag, "Scanning started");
802             if (BuildCompat.isAtLeastU()) {
803                 // Start off with a fast scan of 2.4GHz, 5GHz, and 6GHz RNR using WifiScanner.
804                 // After this is done, fall back to WifiManager.startScan() to get the rest of
805                 // the bands and hidden networks.
806                 // TODO(b/274177966): Move to using WifiScanner exclusively once we have
807                 //                    permission to use ScanSettings.hiddenNetworks.
808                 WifiScanner.ScanSettings scanSettings = new WifiScanner.ScanSettings();
809                 scanSettings.band = WifiScanner.WIFI_BAND_BOTH;
810                 scanSettings.setRnrSetting(WifiScanner.WIFI_RNR_ENABLED);
811                 scanSettings.reportEvents = WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT
812                         | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
813                 WifiScanner wifiScanner = mContext.getSystemService(WifiScanner.class);
814                 if (wifiScanner != null) {
815                     wifiScanner.stopScan(mFirstScanListener);
816                     if (isVerboseLoggingEnabled()) {
817                         Log.v(mTag, "Issuing scan request from WifiScanner");
818                     }
819                     wifiScanner.startScan(scanSettings, mFirstScanListener);
820                     notifyOnScanRequested();
821                     return;
822                 } else {
823                     Log.e(mTag, "Failed to retrieve WifiScanner!");
824                 }
825             }
826             scanLoop();
827         }
828 
829         @WorkerThread
830         private void stopScanning() {
831             Log.i(mTag, "Scanning stopped");
832             removeCallbacksAndMessages(null);
833         }
834 
835         @WorkerThread
836         private void scanLoop() {
837             if (!shouldScan()) {
838                 Log.e(mTag, "Scan loop called even though we shouldn't be scanning!"
839                         + " mIsWifiEnabled=" + mIsWifiEnabled
840                         + " mIsStartedState=" + mIsStartedState);
841                 return;
842             }
843             if (!isAppVisible()) {
844                 Log.e(mTag, "Scan loop called even though app isn't visible anymore!"
845                         + " mIsWifiEnabled=" + mIsWifiEnabled
846                         + " mIsStartedState=" + mIsStartedState);
847                 return;
848             }
849             if (isVerboseLoggingEnabled()) {
850                 Log.v(mTag, "Issuing scan request from WifiManager");
851             }
852             // Remove any pending scanLoops in case possiblyStartScanning was called more than once.
853             removeCallbacksAndMessages(null);
854             mWifiManager.startScan();
855             notifyOnScanRequested();
856             postDelayed(this::scanLoop, mScanIntervalMillis);
857         }
858     }
859 
860     private boolean isAppVisible() {
861         ActivityManager.RunningAppProcessInfo processInfo =
862                 new ActivityManager.RunningAppProcessInfo();
863         ActivityManager.getMyMemoryState(processInfo);
864         return processInfo.importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
865     }
866 
867     /**
868      * Posts onWifiStateChanged callback on the main thread.
869      */
870     @WorkerThread
871     private void notifyOnWifiStateChanged() {
872         if (mListener != null) {
873             mMainHandler.post(mListener::onWifiStateChanged);
874         }
875     }
876 
877     /**
878      * Posts onScanRequested callback on the main thread.
879      */
880     @WorkerThread
881     private void notifyOnScanRequested() {
882         if (mListener != null) {
883             mMainHandler.post(mListener::onScanRequested);
884         }
885     }
886 
887     /**
888      * Base callback handling Wi-Fi state changes
889      *
890      * Subclasses should extend this for their own needs.
891      */
892     protected interface BaseWifiTrackerCallback {
893         /**
894          * Called when the value for {@link #getWifiState() has changed.
895          */
896         @MainThread
897         void onWifiStateChanged();
898 
899         @MainThread
900         default void onScanRequested() {
901             // Do nothing.
902         }
903     }
904 }
905