1 /* 2 * Copyright (C) 2021 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.car.settings.wifi; 18 19 import android.annotation.NonNull; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.net.TetheringManager; 25 import android.net.wifi.SoftApInfo; 26 import android.net.wifi.WifiClient; 27 import android.net.wifi.WifiManager; 28 29 import androidx.annotation.VisibleForTesting; 30 import androidx.lifecycle.Lifecycle; 31 import androidx.localbroadcastmanager.content.LocalBroadcastManager; 32 33 import java.util.List; 34 35 /** 36 * Consolidates Wifi tethering logic into one handler so we can have consistent logic across various 37 * parts of the Settings app. 38 */ 39 public class WifiTetheringHandler { 40 41 private final Context mContext; 42 private final CarWifiManager mCarWifiManager; 43 private final TetheringManager mTetheringManager; 44 private final WifiTetheringAvailabilityListener mWifiTetheringAvailabilityListener; 45 private boolean mRestartBooked = false; 46 47 private final WifiManager.SoftApCallback mSoftApCallback = new WifiManager.SoftApCallback() { 48 @Override 49 public void onStateChanged(int state, int failureReason) { 50 handleWifiApStateChanged(state); 51 } 52 53 @Override 54 public void onConnectedClientsChanged(@NonNull SoftApInfo info, 55 @NonNull List<WifiClient> clients) { 56 mWifiTetheringAvailabilityListener.onConnectedClientsChanged(clients.size()); 57 } 58 }; 59 60 private final BroadcastReceiver mRestartReceiver = new BroadcastReceiver() { 61 @Override 62 public void onReceive(Context context, Intent intent) { 63 if (mCarWifiManager != null && mCarWifiManager.isWifiApEnabled()) { 64 restartTethering(); 65 } 66 } 67 }; 68 WifiTetheringHandler(Context context, Lifecycle lifecycle, WifiTetheringAvailabilityListener wifiTetherAvailabilityListener)69 public WifiTetheringHandler(Context context, Lifecycle lifecycle, 70 WifiTetheringAvailabilityListener wifiTetherAvailabilityListener) { 71 this(context, new CarWifiManager(context, lifecycle), 72 context.getSystemService(TetheringManager.class), wifiTetherAvailabilityListener); 73 } 74 WifiTetheringHandler(Context context, CarWifiManager carWifiManager, TetheringManager tetheringManager, WifiTetheringAvailabilityListener wifiTetherAvailabilityListener)75 public WifiTetheringHandler(Context context, CarWifiManager carWifiManager, 76 TetheringManager tetheringManager, WifiTetheringAvailabilityListener 77 wifiTetherAvailabilityListener) { 78 mContext = context; 79 mCarWifiManager = carWifiManager; 80 mTetheringManager = tetheringManager; 81 mWifiTetheringAvailabilityListener = wifiTetherAvailabilityListener; 82 } 83 84 /** 85 * Handles operations that should happen in host's onStartInternal(). 86 */ onStartInternal()87 public void onStartInternal() { 88 mCarWifiManager.registerSoftApCallback(mContext.getMainExecutor(), mSoftApCallback); 89 LocalBroadcastManager.getInstance(mContext).registerReceiver(mRestartReceiver, 90 new IntentFilter( 91 WifiTetherBasePreferenceController.ACTION_RESTART_WIFI_TETHERING)); 92 } 93 94 /** 95 * Handles operations that should happen in host's onStopInternal(). 96 */ onStopInternal()97 public void onStopInternal() { 98 mCarWifiManager.unregisterSoftApCallback(mSoftApCallback); 99 LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mRestartReceiver); 100 } 101 102 /** 103 * Returns whether wifi tethering is enabled 104 * @return whether wifi tethering is enabled 105 */ isWifiTetheringEnabled()106 public boolean isWifiTetheringEnabled() { 107 return mCarWifiManager.isWifiApEnabled(); 108 } 109 110 /** 111 * Changes the Wifi tethering state 112 * 113 * @param enable Whether to attempt to turn Wifi tethering on or off 114 */ updateWifiTetheringState(boolean enable)115 public void updateWifiTetheringState(boolean enable) { 116 if (enable) { 117 startTethering(); 118 } else { 119 stopTethering(); 120 } 121 } 122 123 @VisibleForTesting handleWifiApStateChanged(int state)124 void handleWifiApStateChanged(int state) { 125 switch (state) { 126 case WifiManager.WIFI_AP_STATE_ENABLING: 127 mWifiTetheringAvailabilityListener.disablePreference(); 128 break; 129 case WifiManager.WIFI_AP_STATE_ENABLED: 130 mWifiTetheringAvailabilityListener.enablePreference(); 131 mWifiTetheringAvailabilityListener.onWifiTetheringAvailable(); 132 break; 133 case WifiManager.WIFI_AP_STATE_DISABLING: 134 mWifiTetheringAvailabilityListener.disablePreference(); 135 mWifiTetheringAvailabilityListener.onWifiTetheringUnavailable(); 136 break; 137 case WifiManager.WIFI_AP_STATE_DISABLED: 138 mWifiTetheringAvailabilityListener.onWifiTetheringUnavailable(); 139 mWifiTetheringAvailabilityListener.enablePreference(); 140 if (mRestartBooked) { 141 // Hotspot was disabled as part of a restart request - we can now re-enable it 142 mWifiTetheringAvailabilityListener.disablePreference(); 143 startTethering(); 144 mRestartBooked = false; 145 } 146 break; 147 default: 148 mWifiTetheringAvailabilityListener.onWifiTetheringUnavailable(); 149 mWifiTetheringAvailabilityListener.enablePreference(); 150 break; 151 } 152 } 153 startTethering()154 private void startTethering() { 155 WifiTetherUtil.startTethering(mTetheringManager, 156 new TetheringManager.StartTetheringCallback() { 157 @Override 158 public void onTetheringFailed(int error) { 159 mWifiTetheringAvailabilityListener.onWifiTetheringUnavailable(); 160 mWifiTetheringAvailabilityListener.enablePreference(); 161 } 162 }); 163 } 164 stopTethering()165 private void stopTethering() { 166 WifiTetherUtil.stopTethering(mTetheringManager); 167 } 168 restartTethering()169 private void restartTethering() { 170 stopTethering(); 171 mRestartBooked = true; 172 } 173 174 /** 175 * Interface for receiving Wifi tethering status updates 176 */ 177 public interface WifiTetheringAvailabilityListener { 178 /** 179 * Callback for when Wifi tethering is available 180 */ onWifiTetheringAvailable()181 void onWifiTetheringAvailable(); 182 183 /** 184 * Callback for when Wifi tethering is unavailable 185 */ onWifiTetheringUnavailable()186 void onWifiTetheringUnavailable(); 187 188 /** 189 * Callback for when the number of tethered devices has changed 190 * @param clientCount number of connected clients 191 */ onConnectedClientsChanged(int clientCount)192 default void onConnectedClientsChanged(int clientCount){ 193 } 194 195 /** 196 * Listener should allow further changes to Wifi tethering 197 */ enablePreference()198 void enablePreference(); 199 200 /** 201 * Listener should disallow further changes to Wifi tethering 202 */ disablePreference()203 void disablePreference(); 204 } 205 } 206