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