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