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