• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.settings.wifi.repository;
18 
19 import static android.net.TetheringManager.TETHERING_WIFI;
20 import static android.net.wifi.SoftApConfiguration.BAND_2GHZ;
21 import static android.net.wifi.SoftApConfiguration.BAND_5GHZ;
22 import static android.net.wifi.SoftApConfiguration.BAND_6GHZ;
23 import static android.net.wifi.SoftApConfiguration.SECURITY_TYPE_OPEN;
24 import static android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA3_SAE;
25 import static android.net.wifi.WifiAvailableChannel.OP_MODE_SAP;
26 import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
27 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
28 
29 import android.content.Context;
30 import android.net.TetheringManager;
31 import android.net.wifi.SoftApConfiguration;
32 import android.net.wifi.WifiAvailableChannel;
33 import android.net.wifi.WifiManager;
34 import android.net.wifi.WifiScanner;
35 import android.text.TextUtils;
36 import android.util.Log;
37 
38 import androidx.annotation.NonNull;
39 import androidx.annotation.VisibleForTesting;
40 import androidx.lifecycle.LiveData;
41 import androidx.lifecycle.MutableLiveData;
42 
43 import com.android.settings.R;
44 import com.android.settings.overlay.FeatureFactory;
45 
46 import java.util.HashMap;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.UUID;
50 import java.util.function.Consumer;
51 
52 /**
53  * Wi-Fi Hotspot Repository
54  */
55 public class WifiHotspotRepository {
56     private static final String TAG = "WifiHotspotRepository";
57 
58     private static final int RESTART_INTERVAL_MS = 100;
59 
60     /** Wi-Fi hotspot band unknown. */
61     public static final int BAND_UNKNOWN = 0;
62     /** Wi-Fi hotspot band 2.4GHz and 5GHz. */
63     public static final int BAND_2GHZ_5GHZ = BAND_2GHZ | BAND_5GHZ;
64     /** Wi-Fi hotspot band 2.4GHz and 5GHz and 6GHz. */
65     public static final int BAND_2GHZ_5GHZ_6GHZ = BAND_2GHZ | BAND_5GHZ | BAND_6GHZ;
66 
67     /** Wi-Fi hotspot speed unknown. */
68     public static final int SPEED_UNKNOWN = 0;
69     /** Wi-Fi hotspot speed 2.4GHz. */
70     public static final int SPEED_2GHZ = 1;
71     /** Wi-Fi hotspot speed 5GHz. */
72     public static final int SPEED_5GHZ = 2;
73     /** Wi-Fi hotspot speed 2.4GHz and 5GHz. */
74     public static final int SPEED_2GHZ_5GHZ = 3;
75     /** Wi-Fi hotspot speed 6GHz. */
76     public static final int SPEED_6GHZ = 4;
77 
78     protected static Map<Integer, Integer> sSpeedMap = new HashMap<>();
79 
80     static {
sSpeedMap.put(BAND_UNKNOWN, SPEED_UNKNOWN)81         sSpeedMap.put(BAND_UNKNOWN, SPEED_UNKNOWN);
sSpeedMap.put(BAND_2GHZ, SPEED_2GHZ)82         sSpeedMap.put(BAND_2GHZ, SPEED_2GHZ);
sSpeedMap.put(BAND_5GHZ, SPEED_5GHZ)83         sSpeedMap.put(BAND_5GHZ, SPEED_5GHZ);
sSpeedMap.put(BAND_6GHZ, SPEED_6GHZ)84         sSpeedMap.put(BAND_6GHZ, SPEED_6GHZ);
sSpeedMap.put(BAND_2GHZ_5GHZ, SPEED_2GHZ_5GHZ)85         sSpeedMap.put(BAND_2GHZ_5GHZ, SPEED_2GHZ_5GHZ);
86     }
87 
88     private final Context mAppContext;
89     private final WifiManager mWifiManager;
90     private final TetheringManager mTetheringManager;
91 
92     protected String mLastPassword;
93     protected LastPasswordListener mLastPasswordListener = new LastPasswordListener();
94 
95     protected MutableLiveData<Integer> mSecurityType;
96     protected MutableLiveData<Integer> mSpeedType;
97 
98     protected Boolean mIsDualBand;
99     protected Boolean mIs5gBandSupported;
100     protected Boolean mIs5gAvailable;
101     protected MutableLiveData<Boolean> m5gAvailable;
102     protected Boolean mIs6gBandSupported;
103     protected Boolean mIs6gAvailable;
104     protected MutableLiveData<Boolean> m6gAvailable;
105     protected ActiveCountryCodeChangedCallback mActiveCountryCodeChangedCallback;
106 
107     @VisibleForTesting
108     Boolean mIsConfigShowSpeed;
109     private Boolean mIsSpeedFeatureAvailable;
110 
111     @VisibleForTesting
112     SoftApCallback mSoftApCallback = new SoftApCallback();
113     @VisibleForTesting
114     StartTetheringCallback mStartTetheringCallback;
115     @VisibleForTesting
116     int mWifiApState = WIFI_AP_STATE_DISABLED;
117 
118     @VisibleForTesting
119     boolean mIsRestarting;
120     @VisibleForTesting
121     MutableLiveData<Boolean> mRestarting;
122 
WifiHotspotRepository(@onNull Context appContext, @NonNull WifiManager wifiManager, @NonNull TetheringManager tetheringManager)123     public WifiHotspotRepository(@NonNull Context appContext, @NonNull WifiManager wifiManager,
124             @NonNull TetheringManager tetheringManager) {
125         mAppContext = appContext;
126         mWifiManager = wifiManager;
127         mTetheringManager = tetheringManager;
128         mWifiManager.registerSoftApCallback(mAppContext.getMainExecutor(), mSoftApCallback);
129     }
130 
131     /**
132      * Query the last configured Tethered Ap Passphrase since boot.
133      */
queryLastPasswordIfNeeded()134     public void queryLastPasswordIfNeeded() {
135         SoftApConfiguration config = mWifiManager.getSoftApConfiguration();
136         if (config.getSecurityType() != SoftApConfiguration.SECURITY_TYPE_OPEN) {
137             return;
138         }
139         mWifiManager.queryLastConfiguredTetheredApPassphraseSinceBoot(mAppContext.getMainExecutor(),
140                 mLastPasswordListener);
141     }
142 
143     /**
144      * Generate password.
145      */
generatePassword()146     public String generatePassword() {
147         return !TextUtils.isEmpty(mLastPassword) ? mLastPassword : generateRandomPassword();
148     }
149 
150     @VisibleForTesting
generatePassword(SoftApConfiguration config)151     String generatePassword(SoftApConfiguration config) {
152         String password = config.getPassphrase();
153         if (TextUtils.isEmpty(password)) {
154             password = generatePassword();
155         }
156         return password;
157     }
158 
159     private class LastPasswordListener implements Consumer<String> {
160         @Override
accept(String password)161         public void accept(String password) {
162             mLastPassword = password;
163         }
164     }
165 
generateRandomPassword()166     private static String generateRandomPassword() {
167         String randomUUID = UUID.randomUUID().toString();
168         //first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
169         return randomUUID.substring(0, 8) + randomUUID.substring(9, 13);
170     }
171 
172     /**
173      * Gets the Wi-Fi tethered AP Configuration.
174      *
175      * @return AP details in {@link SoftApConfiguration}
176      */
getSoftApConfiguration()177     public SoftApConfiguration getSoftApConfiguration() {
178         return mWifiManager.getSoftApConfiguration();
179     }
180 
181     /**
182      * Sets the tethered Wi-Fi AP Configuration.
183      *
184      * @param config A valid SoftApConfiguration specifying the configuration of the SAP.
185      */
setSoftApConfiguration(@onNull SoftApConfiguration config)186     public void setSoftApConfiguration(@NonNull SoftApConfiguration config) {
187         if (mIsRestarting) {
188             Log.e(TAG, "Skip setSoftApConfiguration because hotspot is restarting.");
189             return;
190         }
191         mWifiManager.setSoftApConfiguration(config);
192         refresh();
193         restartTetheringIfNeeded();
194     }
195 
196     /**
197      * Refresh data from the SoftApConfiguration.
198      */
refresh()199     public void refresh() {
200         updateSecurityType();
201         update6gAvailable();
202         update5gAvailable();
203         updateSpeedType();
204     }
205 
206     /**
207      * Set to auto refresh data.
208      *
209      * @param enabled whether the auto refresh should be enabled or not.
210      */
setAutoRefresh(boolean enabled)211     public void setAutoRefresh(boolean enabled) {
212         if (enabled) {
213             startAutoRefresh();
214         } else {
215             stopAutoRefresh();
216         }
217     }
218 
219     /**
220      * Gets SecurityType LiveData
221      */
getSecurityType()222     public LiveData<Integer> getSecurityType() {
223         if (mSecurityType == null) {
224             startAutoRefresh();
225             mSecurityType = new MutableLiveData<>();
226             updateSecurityType();
227             log("getSecurityType():" + mSecurityType.getValue());
228         }
229         return mSecurityType;
230     }
231 
updateSecurityType()232     protected void updateSecurityType() {
233         if (mSecurityType == null) {
234             return;
235         }
236         SoftApConfiguration config = mWifiManager.getSoftApConfiguration();
237         int securityType = (config != null) ? config.getSecurityType() : SECURITY_TYPE_OPEN;
238         log("updateSecurityType(), securityType:" + securityType);
239         mSecurityType.setValue(securityType);
240     }
241 
242     /**
243      * Sets SecurityType
244      *
245      * @param securityType the Wi-Fi hotspot security type.
246      */
setSecurityType(int securityType)247     public void setSecurityType(int securityType) {
248         log("setSecurityType():" + securityType);
249         if (mSecurityType == null) {
250             getSecurityType();
251         }
252         if (securityType == mSecurityType.getValue()) {
253             Log.w(TAG, "setSecurityType() is no changed! mSecurityType:"
254                     + mSecurityType.getValue());
255             return;
256         }
257         SoftApConfiguration config = mWifiManager.getSoftApConfiguration();
258         if (config == null) {
259             mSecurityType.setValue(SECURITY_TYPE_OPEN);
260             Log.e(TAG, "setSecurityType(), WifiManager#getSoftApConfiguration() return null!");
261             return;
262         }
263         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(config);
264         String passphrase = (securityType == SECURITY_TYPE_OPEN) ? null : generatePassword(config);
265         configBuilder.setPassphrase(passphrase, securityType);
266         setSoftApConfiguration(configBuilder.build());
267 
268         mWifiManager.queryLastConfiguredTetheredApPassphraseSinceBoot(
269                 mAppContext.getMainExecutor(), mLastPasswordListener);
270     }
271 
272     /**
273      * Gets SpeedType LiveData
274      */
getSpeedType()275     public LiveData<Integer> getSpeedType() {
276         if (mSpeedType == null) {
277             startAutoRefresh();
278             mSpeedType = new MutableLiveData<>();
279             updateSpeedType();
280             log("getSpeedType():" + mSpeedType.getValue());
281         }
282         return mSpeedType;
283     }
284 
updateSpeedType()285     protected void updateSpeedType() {
286         if (mSpeedType == null) {
287             return;
288         }
289         SoftApConfiguration config = mWifiManager.getSoftApConfiguration();
290         if (config == null) {
291             mSpeedType.setValue(SPEED_UNKNOWN);
292             return;
293         }
294         int keyBand = config.getBand();
295         log("updateSpeedType(), getBand():" + keyBand);
296         if (!is5gAvailable()) {
297             keyBand &= ~BAND_5GHZ;
298         }
299         if (!is6gAvailable()) {
300             keyBand &= ~BAND_6GHZ;
301         }
302         if ((keyBand & BAND_6GHZ) != 0) {
303             keyBand = BAND_6GHZ;
304         } else if (isDualBand() && is5gAvailable()) {
305             keyBand = BAND_2GHZ_5GHZ;
306         } else if ((keyBand & BAND_5GHZ) != 0) {
307             keyBand = BAND_5GHZ;
308         } else if ((keyBand & BAND_2GHZ) != 0) {
309             keyBand = BAND_2GHZ;
310         } else {
311             keyBand = 0;
312         }
313         log("updateSpeedType(), keyBand:" + keyBand);
314         mSpeedType.setValue(sSpeedMap.get(keyBand));
315     }
316 
317     /**
318      * Sets SpeedType
319      *
320      * @param speedType the Wi-Fi hotspot speed type.
321      */
setSpeedType(int speedType)322     public void setSpeedType(int speedType) {
323         log("setSpeedType():" + speedType);
324         if (mSpeedType == null) {
325             getSpeedType();
326         }
327         if (speedType == mSpeedType.getValue()) {
328             Log.w(TAG, "setSpeedType() is no changed! mSpeedType:" + mSpeedType.getValue());
329             return;
330         }
331         SoftApConfiguration config = mWifiManager.getSoftApConfiguration();
332         if (config == null) {
333             mSpeedType.setValue(SPEED_UNKNOWN);
334             Log.e(TAG, "setSpeedType(), WifiManager#getSoftApConfiguration() return null!");
335             return;
336         }
337         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(config);
338         if (speedType == SPEED_6GHZ) {
339             log("setSpeedType(), setBand(BAND_2GHZ_5GHZ_6GHZ)");
340             configBuilder.setBand(BAND_2GHZ_5GHZ_6GHZ);
341             if (config.getSecurityType() != SECURITY_TYPE_WPA3_SAE) {
342                 log("setSpeedType(), setPassphrase(SECURITY_TYPE_WPA3_SAE)");
343                 configBuilder.setPassphrase(generatePassword(config), SECURITY_TYPE_WPA3_SAE);
344             }
345         } else if (speedType == SPEED_5GHZ) {
346             log("setSpeedType(), setBand(BAND_2GHZ_5GHZ)");
347             configBuilder.setBand(BAND_2GHZ_5GHZ);
348         } else if (mIsDualBand) {
349             log("setSpeedType(), setBands(BAND_2GHZ + BAND_2GHZ_5GHZ)");
350             int[] bands = {BAND_2GHZ, BAND_2GHZ_5GHZ};
351             configBuilder.setBands(bands);
352         } else {
353             log("setSpeedType(), setBand(BAND_2GHZ)");
354             configBuilder.setBand(BAND_2GHZ);
355         }
356         setSoftApConfiguration(configBuilder.build());
357     }
358 
359     /**
360      * Return whether Wi-Fi Dual Band is supported or not.
361      *
362      * @return {@code true} if Wi-Fi Dual Band is supported
363      */
isDualBand()364     public boolean isDualBand() {
365         if (mIsDualBand == null) {
366             mIsDualBand = mWifiManager.isBridgedApConcurrencySupported();
367             log("isDualBand():" + mIsDualBand);
368         }
369         return mIsDualBand;
370     }
371 
372     /**
373      * Return whether Wi-Fi 5 GHz band is supported or not.
374      *
375      * @return {@code true} if Wi-Fi 5 GHz Band is supported
376      */
is5GHzBandSupported()377     public boolean is5GHzBandSupported() {
378         if (mIs5gBandSupported == null) {
379             mIs5gBandSupported = mWifiManager.is5GHzBandSupported();
380             log("is5GHzBandSupported():" + mIs5gBandSupported);
381         }
382         return mIs5gBandSupported;
383     }
384 
385     /**
386      * Return whether Wi-Fi Hotspot 5 GHz band is available or not.
387      *
388      * @return {@code true} if Wi-Fi Hotspot 5 GHz Band is available
389      */
is5gAvailable()390     public boolean is5gAvailable() {
391         if (mIs5gAvailable == null) {
392             // If Settings is unable to get available 5GHz SAP information, Wi-Fi Framework's
393             // proposal is to assume that 5GHz is available. (See b/272450463#comment16)
394             mIs5gAvailable = is5GHzBandSupported()
395                     && isChannelAvailable(WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS,
396                     true /* defaultValue */);
397             log("is5gAvailable():" + mIs5gAvailable);
398         }
399         return mIs5gAvailable;
400     }
401 
402     /**
403      * Gets is5gAvailable LiveData
404      */
get5gAvailable()405     public LiveData<Boolean> get5gAvailable() {
406         if (m5gAvailable == null) {
407             m5gAvailable = new MutableLiveData<>();
408             m5gAvailable.setValue(is5gAvailable());
409         }
410         return m5gAvailable;
411     }
412 
update5gAvailable()413     protected void update5gAvailable() {
414         if (m5gAvailable != null) {
415             m5gAvailable.setValue(is5gAvailable());
416         }
417     }
418 
419     /**
420      * Return whether Wi-Fi 6 GHz band is supported or not.
421      *
422      * @return {@code true} if Wi-Fi 6 GHz Band is supported
423      */
is6GHzBandSupported()424     public boolean is6GHzBandSupported() {
425         if (mIs6gBandSupported == null) {
426             mIs6gBandSupported = mWifiManager.is6GHzBandSupported();
427             log("is6GHzBandSupported():" + mIs6gBandSupported);
428         }
429         return mIs6gBandSupported;
430     }
431 
432     /**
433      * Return whether Wi-Fi Hotspot 6 GHz band is available or not.
434      *
435      * @return {@code true} if Wi-Fi Hotspot 6 GHz Band is available
436      */
is6gAvailable()437     public boolean is6gAvailable() {
438         if (mIs6gAvailable == null) {
439             mIs6gAvailable = is6GHzBandSupported()
440                     && isChannelAvailable(WifiScanner.WIFI_BAND_6_GHZ, false /* defaultValue */);
441             log("is6gAvailable():" + mIs6gAvailable);
442         }
443         return mIs6gAvailable;
444     }
445 
446     /**
447      * Gets is6gAvailable LiveData
448      */
get6gAvailable()449     public LiveData<Boolean> get6gAvailable() {
450         if (m6gAvailable == null) {
451             m6gAvailable = new MutableLiveData<>();
452             m6gAvailable.setValue(is6gAvailable());
453         }
454         return m6gAvailable;
455     }
456 
update6gAvailable()457     protected void update6gAvailable() {
458         if (m6gAvailable != null) {
459             m6gAvailable.setValue(is6gAvailable());
460         }
461     }
462 
463     /**
464      * Return whether the Hotspot channel is available or not.
465      *
466      * @param band         one of the following band constants defined in
467      *                     {@code WifiScanner#WIFI_BAND_*} constants.
468      *                     1. {@code WifiScanner#WIFI_BAND_5_GHZ_WITH_DFS}
469      *                     2. {@code WifiScanner#WIFI_BAND_6_GHZ}
470      * @param defaultValue returns the default value if WifiManager#getUsableChannels is
471      *                     unavailable to get the SAP information.
472      */
isChannelAvailable(int band, boolean defaultValue)473     protected boolean isChannelAvailable(int band, boolean defaultValue) {
474         try {
475             List<WifiAvailableChannel> channels = mWifiManager.getUsableChannels(band, OP_MODE_SAP);
476             log("isChannelAvailable(), band:" + band + ", channels:" + channels);
477             return (channels != null && channels.size() > 0);
478         } catch (IllegalArgumentException e) {
479             Log.e(TAG, "Querying usable SAP channels failed, band:" + band);
480         } catch (UnsupportedOperationException e) {
481             // This is expected on some hardware.
482             Log.e(TAG, "Querying usable SAP channels is unsupported, band:" + band);
483         }
484         // Disable Wi-Fi hotspot speed feature if an error occurs while getting usable channels.
485         mIsSpeedFeatureAvailable = false;
486         Log.w(TAG, "isChannelAvailable(): Wi-Fi hotspot speed feature disabled");
487         return defaultValue;
488     }
489 
isConfigShowSpeed()490     private boolean isConfigShowSpeed() {
491         if (mIsConfigShowSpeed == null) {
492             mIsConfigShowSpeed = mAppContext.getResources()
493                     .getBoolean(R.bool.config_show_wifi_hotspot_speed);
494             log("isConfigShowSpeed():" + mIsConfigShowSpeed);
495         }
496         return mIsConfigShowSpeed;
497     }
498 
499     /**
500      * Return whether Wi-Fi Hotspot Speed Feature is available or not.
501      *
502      * @return {@code true} if Wi-Fi Hotspot Speed Feature is available
503      */
isSpeedFeatureAvailable()504     public boolean isSpeedFeatureAvailable() {
505         if (mIsSpeedFeatureAvailable != null) {
506             return mIsSpeedFeatureAvailable;
507         }
508 
509         // Check config to show Wi-Fi hotspot speed feature
510         if (!isConfigShowSpeed()) {
511             mIsSpeedFeatureAvailable = false;
512             log("isSpeedFeatureAvailable():false, isConfigShowSpeed():false");
513             return false;
514         }
515 
516         // Check if 5 GHz band is not supported
517         if (!is5GHzBandSupported()) {
518             mIsSpeedFeatureAvailable = false;
519             log("isSpeedFeatureAvailable():false, 5 GHz band is not supported on this device");
520             return false;
521         }
522         // Check if 5 GHz band SAP channel is not ready
523         isChannelAvailable(WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS, true /* defaultValue */);
524         if (mIsSpeedFeatureAvailable != null && !mIsSpeedFeatureAvailable) {
525             log("isSpeedFeatureAvailable():false, error occurred while getting 5 GHz SAP channel");
526             return false;
527         }
528 
529         // Check if 6 GHz band SAP channel is not ready
530         isChannelAvailable(WifiScanner.WIFI_BAND_6_GHZ, false /* defaultValue */);
531         if (mIsSpeedFeatureAvailable != null && !mIsSpeedFeatureAvailable) {
532             log("isSpeedFeatureAvailable():false, error occurred while getting 6 GHz SAP channel");
533             return false;
534         }
535 
536         mIsSpeedFeatureAvailable = true;
537         log("isSpeedFeatureAvailable():true");
538         return true;
539     }
540 
purgeRefreshData()541     protected void purgeRefreshData() {
542         mIs5gAvailable = null;
543         mIs6gAvailable = null;
544     }
545 
startAutoRefresh()546     protected void startAutoRefresh() {
547         if (mActiveCountryCodeChangedCallback != null) {
548             return;
549         }
550         log("startMonitorSoftApConfiguration()");
551         mActiveCountryCodeChangedCallback = new ActiveCountryCodeChangedCallback();
552         mWifiManager.registerActiveCountryCodeChangedCallback(mAppContext.getMainExecutor(),
553                 mActiveCountryCodeChangedCallback);
554     }
555 
stopAutoRefresh()556     protected void stopAutoRefresh() {
557         if (mActiveCountryCodeChangedCallback == null) {
558             return;
559         }
560         log("stopMonitorSoftApConfiguration()");
561         mWifiManager.unregisterActiveCountryCodeChangedCallback(mActiveCountryCodeChangedCallback);
562         mActiveCountryCodeChangedCallback = null;
563     }
564 
565     protected class ActiveCountryCodeChangedCallback implements
566             WifiManager.ActiveCountryCodeChangedCallback {
567         @Override
onActiveCountryCodeChanged(String country)568         public void onActiveCountryCodeChanged(String country) {
569             log("onActiveCountryCodeChanged(), country:" + country);
570             purgeRefreshData();
571             refresh();
572         }
573 
574         @Override
onCountryCodeInactive()575         public void onCountryCodeInactive() {
576         }
577     }
578 
579     /**
580      * Gets Restarting LiveData
581      */
getRestarting()582     public LiveData<Boolean> getRestarting() {
583         if (mRestarting == null) {
584             mRestarting = new MutableLiveData<>();
585             mRestarting.setValue(mIsRestarting);
586         }
587         return mRestarting;
588     }
589 
setRestarting(boolean isRestarting)590     private void setRestarting(boolean isRestarting) {
591         log("setRestarting(), isRestarting:" + isRestarting);
592         mIsRestarting = isRestarting;
593         if (mRestarting != null) {
594             mRestarting.setValue(mIsRestarting);
595         }
596     }
597 
598     @VisibleForTesting
restartTetheringIfNeeded()599     void restartTetheringIfNeeded() {
600         if (mWifiApState != WIFI_AP_STATE_ENABLED) {
601             return;
602         }
603         log("restartTetheringIfNeeded()");
604         mAppContext.getMainThreadHandler().postDelayed(() -> {
605             setRestarting(true);
606             stopTethering();
607         }, RESTART_INTERVAL_MS);
608     }
609 
startTethering()610     private void startTethering() {
611         if (mStartTetheringCallback == null) {
612             mStartTetheringCallback = new StartTetheringCallback();
613         }
614         log("startTethering()");
615         mTetheringManager.startTethering(TETHERING_WIFI, mAppContext.getMainExecutor(),
616                 mStartTetheringCallback);
617     }
618 
stopTethering()619     private void stopTethering() {
620         log("startTethering()");
621         mTetheringManager.stopTethering(TETHERING_WIFI);
622     }
623 
624     @VisibleForTesting
625     class SoftApCallback implements WifiManager.SoftApCallback {
626         private static final String TAG = "SoftApCallback";
627 
628         @Override
onStateChanged(int state, int failureReason)629         public void onStateChanged(int state, int failureReason) {
630             Log.d(TAG, "onStateChanged(), state:" + state + ", failureReason:" + failureReason);
631             mWifiApState = state;
632             if (!mIsRestarting) {
633                 return;
634             }
635             if (state == WIFI_AP_STATE_DISABLED) {
636                 mAppContext.getMainThreadHandler().postDelayed(() -> startTethering(),
637                         RESTART_INTERVAL_MS);
638                 return;
639             }
640             if (state == WIFI_AP_STATE_ENABLED) {
641                 refresh();
642                 setRestarting(false);
643             }
644         }
645     }
646 
647     private class StartTetheringCallback implements TetheringManager.StartTetheringCallback {
648         @Override
onTetheringStarted()649         public void onTetheringStarted() {
650             log("onTetheringStarted()");
651         }
652 
653         @Override
onTetheringFailed(int error)654         public void onTetheringFailed(int error) {
655             log("onTetheringFailed(), error:" + error);
656         }
657     }
658 
log(String msg)659     private void log(String msg) {
660         FeatureFactory.getFactory(mAppContext).getWifiFeatureProvider().verboseLog(TAG, msg);
661     }
662 }
663