1/* 2 * Copyright (C) 2016 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 17package com.android.server.wifi; 18 19import static com.android.server.wifi.util.ApConfigUtil.ERROR_GENERIC; 20import static com.android.server.wifi.util.ApConfigUtil.ERROR_NO_CHANNEL; 21import static com.android.server.wifi.util.ApConfigUtil.SUCCESS; 22 23import android.content.BroadcastReceiver; 24import android.content.Context; 25import android.content.Intent; 26import android.content.IntentFilter; 27import android.net.ConnectivityManager; 28import android.net.InterfaceConfiguration; 29import android.net.LinkAddress; 30import android.net.NetworkUtils; 31import android.net.wifi.WifiConfiguration; 32import android.net.wifi.WifiManager; 33import android.os.INetworkManagementService; 34import android.os.Looper; 35import android.os.Message; 36import android.util.Log; 37 38import com.android.internal.util.State; 39import com.android.internal.util.StateMachine; 40import com.android.server.wifi.util.ApConfigUtil; 41 42import java.util.ArrayList; 43import java.util.Locale; 44 45/** 46 * Manage WiFi in AP mode. 47 * The internal state machine runs under "WifiStateMachine" thread context. 48 */ 49public class SoftApManager { 50 private static final String TAG = "SoftApManager"; 51 52 private final Context mContext; 53 private final INetworkManagementService mNmService; 54 private final WifiNative mWifiNative; 55 private final ConnectivityManager mConnectivityManager; 56 private final ArrayList<Integer> mAllowed2GChannels; 57 58 private final String mCountryCode; 59 60 private final String mInterfaceName; 61 private String mTetherInterfaceName; 62 63 private final SoftApStateMachine mStateMachine; 64 65 private final Listener mListener; 66 67 private static class TetherStateChange { 68 public ArrayList<String> available; 69 public ArrayList<String> active; 70 71 TetherStateChange(ArrayList<String> av, ArrayList<String> ac) { 72 available = av; 73 active = ac; 74 } 75 } 76 77 /** 78 * Listener for soft AP state changes. 79 */ 80 public interface Listener { 81 /** 82 * Invoke when AP state changed. 83 * @param state new AP state 84 * @param failureReason reason when in failed state 85 */ 86 void onStateChanged(int state, int failureReason); 87 } 88 89 public SoftApManager(Context context, 90 Looper looper, 91 WifiNative wifiNative, 92 INetworkManagementService nmService, 93 ConnectivityManager connectivityManager, 94 String countryCode, 95 ArrayList<Integer> allowed2GChannels, 96 Listener listener) { 97 mStateMachine = new SoftApStateMachine(looper); 98 99 mContext = context; 100 mNmService = nmService; 101 mWifiNative = wifiNative; 102 mConnectivityManager = connectivityManager; 103 mCountryCode = countryCode; 104 mAllowed2GChannels = allowed2GChannels; 105 mListener = listener; 106 107 mInterfaceName = mWifiNative.getInterfaceName(); 108 109 /* Register receiver for tether state changes. */ 110 mContext.registerReceiver( 111 new BroadcastReceiver() { 112 @Override 113 public void onReceive(Context context, Intent intent) { 114 ArrayList<String> available = intent.getStringArrayListExtra( 115 ConnectivityManager.EXTRA_AVAILABLE_TETHER); 116 ArrayList<String> active = intent.getStringArrayListExtra( 117 ConnectivityManager.EXTRA_ACTIVE_TETHER); 118 mStateMachine.sendMessage( 119 SoftApStateMachine.CMD_TETHER_STATE_CHANGE, 120 new TetherStateChange(available, active)); 121 } 122 }, new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED)); 123 } 124 125 /** 126 * Start soft AP with given configuration. 127 * @param config AP configuration 128 */ 129 public void start(WifiConfiguration config) { 130 mStateMachine.sendMessage(SoftApStateMachine.CMD_START, config); 131 } 132 133 /** 134 * Stop soft AP. 135 */ 136 public void stop() { 137 mStateMachine.sendMessage(SoftApStateMachine.CMD_STOP); 138 } 139 140 /** 141 * Update AP state. 142 * @param state new AP state 143 * @param reason Failure reason if the new AP state is in failure state 144 */ 145 private void updateApState(int state, int reason) { 146 if (mListener != null) { 147 mListener.onStateChanged(state, reason); 148 } 149 } 150 151 /** 152 * Start a soft AP instance with the given configuration. 153 * @param config AP configuration 154 * @return integer result code 155 */ 156 private int startSoftAp(WifiConfiguration config) { 157 if (config == null) { 158 Log.e(TAG, "Unable to start soft AP without configuration"); 159 return ERROR_GENERIC; 160 } 161 162 /* Make a copy of configuration for updating AP band and channel. */ 163 WifiConfiguration localConfig = new WifiConfiguration(config); 164 165 int result = ApConfigUtil.updateApChannelConfig( 166 mWifiNative, mCountryCode, mAllowed2GChannels, localConfig); 167 if (result != SUCCESS) { 168 Log.e(TAG, "Failed to update AP band and channel"); 169 return result; 170 } 171 172 /* Setup country code if it is provide. */ 173 if (mCountryCode != null) { 174 /** 175 * Country code is mandatory for 5GHz band, return an error if failed to set 176 * country code when AP is configured for 5GHz band. 177 */ 178 if (!mWifiNative.setCountryCodeHal(mCountryCode.toUpperCase(Locale.ROOT)) 179 && config.apBand == WifiConfiguration.AP_BAND_5GHZ) { 180 Log.e(TAG, "Failed to set country code, required for setting up " 181 + "soft ap in 5GHz"); 182 return ERROR_GENERIC; 183 } 184 } 185 186 try { 187 mNmService.startAccessPoint(localConfig, mInterfaceName); 188 } catch (Exception e) { 189 Log.e(TAG, "Exception in starting soft AP: " + e); 190 return ERROR_GENERIC; 191 } 192 193 Log.d(TAG, "Soft AP is started"); 194 195 return SUCCESS; 196 } 197 198 /** 199 * Teardown soft AP. 200 */ 201 private void stopSoftAp() { 202 try { 203 mNmService.stopAccessPoint(mInterfaceName); 204 } catch (Exception e) { 205 Log.e(TAG, "Exception in stopping soft AP: " + e); 206 return; 207 } 208 Log.d(TAG, "Soft AP is stopped"); 209 } 210 211 private boolean startTethering(ArrayList<String> available) { 212 String[] wifiRegexs = mConnectivityManager.getTetherableWifiRegexs(); 213 214 for (String intf : available) { 215 for (String regex : wifiRegexs) { 216 if (intf.matches(regex)) { 217 try { 218 InterfaceConfiguration ifcg = 219 mNmService.getInterfaceConfig(intf); 220 if (ifcg != null) { 221 /* IP/netmask: 192.168.43.1/255.255.255.0 */ 222 ifcg.setLinkAddress(new LinkAddress( 223 NetworkUtils.numericToInetAddress("192.168.43.1"), 24)); 224 ifcg.setInterfaceUp(); 225 226 mNmService.setInterfaceConfig(intf, ifcg); 227 } 228 } catch (Exception e) { 229 Log.e(TAG, "Error configuring interface " + intf + ", :" + e); 230 return false; 231 } 232 233 if (mConnectivityManager.tether(intf) 234 != ConnectivityManager.TETHER_ERROR_NO_ERROR) { 235 Log.e(TAG, "Error tethering on " + intf); 236 return false; 237 } 238 mTetherInterfaceName = intf; 239 return true; 240 } 241 } 242 } 243 /* We found no interfaces to tether. */ 244 return false; 245 } 246 247 private void stopTethering() { 248 try { 249 /* Clear the interface address. */ 250 InterfaceConfiguration ifcg = 251 mNmService.getInterfaceConfig(mTetherInterfaceName); 252 if (ifcg != null) { 253 ifcg.setLinkAddress( 254 new LinkAddress( 255 NetworkUtils.numericToInetAddress("0.0.0.0"), 0)); 256 mNmService.setInterfaceConfig(mTetherInterfaceName, ifcg); 257 } 258 } catch (Exception e) { 259 Log.e(TAG, "Error resetting interface " + mTetherInterfaceName + ", :" + e); 260 } 261 262 if (mConnectivityManager.untether(mTetherInterfaceName) 263 != ConnectivityManager.TETHER_ERROR_NO_ERROR) { 264 Log.e(TAG, "Untether initiate failed!"); 265 } 266 } 267 268 private boolean isWifiTethered(ArrayList<String> active) { 269 String[] wifiRegexs = mConnectivityManager.getTetherableWifiRegexs(); 270 for (String intf : active) { 271 for (String regex : wifiRegexs) { 272 if (intf.matches(regex)) { 273 return true; 274 } 275 } 276 } 277 /* No tethered interface. */ 278 return false; 279 } 280 281 private class SoftApStateMachine extends StateMachine { 282 /* Commands for the state machine. */ 283 public static final int CMD_START = 0; 284 public static final int CMD_STOP = 1; 285 public static final int CMD_TETHER_STATE_CHANGE = 2; 286 public static final int CMD_TETHER_NOTIFICATION_TIMEOUT = 3; 287 288 private static final int TETHER_NOTIFICATION_TIME_OUT_MSECS = 5000; 289 290 /* Sequence number used to track tether notification timeout. */ 291 private int mTetherToken = 0; 292 293 private final State mIdleState = new IdleState(); 294 private final State mStartedState = new StartedState(); 295 private final State mTetheringState = new TetheringState(); 296 private final State mTetheredState = new TetheredState(); 297 private final State mUntetheringState = new UntetheringState(); 298 299 SoftApStateMachine(Looper looper) { 300 super(TAG, looper); 301 302 // CHECKSTYLE:OFF IndentationCheck 303 addState(mIdleState); 304 addState(mStartedState, mIdleState); 305 addState(mTetheringState, mStartedState); 306 addState(mTetheredState, mStartedState); 307 addState(mUntetheringState, mStartedState); 308 // CHECKSTYLE:ON IndentationCheck 309 310 setInitialState(mIdleState); 311 start(); 312 } 313 314 private class IdleState extends State { 315 @Override 316 public boolean processMessage(Message message) { 317 switch (message.what) { 318 case CMD_START: 319 updateApState(WifiManager.WIFI_AP_STATE_ENABLING, 0); 320 int result = startSoftAp((WifiConfiguration) message.obj); 321 if (result == SUCCESS) { 322 updateApState(WifiManager.WIFI_AP_STATE_ENABLED, 0); 323 transitionTo(mStartedState); 324 } else { 325 int reason = WifiManager.SAP_START_FAILURE_GENERAL; 326 if (result == ERROR_NO_CHANNEL) { 327 reason = WifiManager.SAP_START_FAILURE_NO_CHANNEL; 328 } 329 updateApState(WifiManager.WIFI_AP_STATE_FAILED, reason); 330 } 331 break; 332 default: 333 /* Ignore all other commands. */ 334 break; 335 } 336 return HANDLED; 337 } 338 } 339 340 private class StartedState extends State { 341 @Override 342 public boolean processMessage(Message message) { 343 switch (message.what) { 344 case CMD_START: 345 /* Already started, ignore this command. */ 346 break; 347 case CMD_STOP: 348 updateApState(WifiManager.WIFI_AP_STATE_DISABLING, 0); 349 stopSoftAp(); 350 updateApState(WifiManager.WIFI_AP_STATE_DISABLED, 0); 351 transitionTo(mIdleState); 352 break; 353 case CMD_TETHER_STATE_CHANGE: 354 TetherStateChange stateChange = (TetherStateChange) message.obj; 355 if (startTethering(stateChange.available)) { 356 transitionTo(mTetheringState); 357 } 358 break; 359 default: 360 return NOT_HANDLED; 361 } 362 return HANDLED; 363 } 364 } 365 366 /** 367 * This is a transient state. We will transition out of this state when 368 * we receive a notification that WiFi is tethered (TetheredState) or 369 * we timed out waiting for that notification (StartedState). 370 */ 371 private class TetheringState extends State { 372 @Override 373 public void enter() { 374 /* Send a delayed message to terminate if tethering fails to notify. */ 375 sendMessageDelayed( 376 obtainMessage(CMD_TETHER_NOTIFICATION_TIMEOUT, ++mTetherToken), 377 TETHER_NOTIFICATION_TIME_OUT_MSECS); 378 } 379 380 @Override 381 public boolean processMessage(Message message) { 382 switch (message.what) { 383 case CMD_TETHER_STATE_CHANGE: 384 TetherStateChange stateChange = (TetherStateChange) message.obj; 385 if (isWifiTethered(stateChange.active)) { 386 transitionTo(mTetheredState); 387 } 388 break; 389 case CMD_TETHER_NOTIFICATION_TIMEOUT: 390 if (message.arg1 == mTetherToken) { 391 Log.e(TAG, "Failed to get tether update, " 392 + "shutdown soft access point"); 393 transitionTo(mStartedState); 394 /* Needs to be first thing handled. */ 395 sendMessageAtFrontOfQueue(CMD_STOP); 396 } 397 break; 398 default: 399 return NOT_HANDLED; 400 } 401 return HANDLED; 402 } 403 } 404 405 private class TetheredState extends State { 406 @Override 407 public boolean processMessage(Message message) { 408 switch (message.what) { 409 case CMD_TETHER_STATE_CHANGE: 410 TetherStateChange stateChange = (TetherStateChange) message.obj; 411 if (!isWifiTethered(stateChange.active)) { 412 Log.e(TAG, "Tethering reports wifi as untethered!, " 413 + "shut down soft Ap"); 414 sendMessage(CMD_STOP); 415 } 416 break; 417 case CMD_STOP: 418 Log.d(TAG, "Untethering before stopping AP"); 419 stopTethering(); 420 transitionTo(mUntetheringState); 421 break; 422 423 default: 424 return NOT_HANDLED; 425 } 426 return HANDLED; 427 } 428 } 429 430 /** 431 * This is a transient state, will transition out of this state to StartedState 432 * when we receive a notification that WiFi is untethered or we timed out waiting 433 * for that notification. 434 */ 435 private class UntetheringState extends State { 436 @Override 437 public void enter() { 438 /* Send a delayed message to terminate if tethering fails to notify. */ 439 sendMessageDelayed( 440 obtainMessage(CMD_TETHER_NOTIFICATION_TIMEOUT, ++mTetherToken), 441 TETHER_NOTIFICATION_TIME_OUT_MSECS); 442 } 443 444 @Override 445 public boolean processMessage(Message message) { 446 switch (message.what) { 447 case CMD_TETHER_STATE_CHANGE: 448 TetherStateChange stateChange = (TetherStateChange) message.obj; 449 /* Transition back to StartedState when WiFi is untethered. */ 450 if (!isWifiTethered(stateChange.active)) { 451 transitionTo(mStartedState); 452 /* Needs to be first thing handled */ 453 sendMessageAtFrontOfQueue(CMD_STOP); 454 } 455 break; 456 case CMD_TETHER_NOTIFICATION_TIMEOUT: 457 if (message.arg1 == mTetherToken) { 458 Log.e(TAG, "Failed to get tether update, " 459 + "force stop access point"); 460 transitionTo(mStartedState); 461 /* Needs to be first thing handled. */ 462 sendMessageAtFrontOfQueue(CMD_STOP); 463 } 464 break; 465 default: 466 /* Defer handling of this message until untethering is completed. */ 467 deferMessage(message); 468 break; 469 } 470 return HANDLED; 471 } 472 } 473 } 474} 475