/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wifi; import static com.android.server.wifi.util.ApConfigUtil.ERROR_GENERIC; import static com.android.server.wifi.util.ApConfigUtil.ERROR_NO_CHANNEL; import static com.android.server.wifi.util.ApConfigUtil.SUCCESS; import android.net.InterfaceConfiguration; import android.net.wifi.IApInterface; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.KeyMgmt; import android.net.wifi.WifiManager; import android.os.INetworkManagementService; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.util.Log; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.server.net.BaseNetworkObserver; import com.android.server.wifi.util.ApConfigUtil; import java.nio.charset.StandardCharsets; import java.util.Locale; /** * Manage WiFi in AP mode. * The internal state machine runs under "WifiStateMachine" thread context. */ public class SoftApManager implements ActiveModeManager { private static final String TAG = "SoftApManager"; private final WifiNative mWifiNative; private final String mCountryCode; private final SoftApStateMachine mStateMachine; private final Listener mListener; private final IApInterface mApInterface; private final INetworkManagementService mNwService; private final WifiApConfigStore mWifiApConfigStore; private final WifiMetrics mWifiMetrics; private WifiConfiguration mApConfig; /** * Listener for soft AP state changes. */ public interface Listener { /** * Invoke when AP state changed. * @param state new AP state * @param failureReason reason when in failed state */ void onStateChanged(int state, int failureReason); } public SoftApManager(Looper looper, WifiNative wifiNative, String countryCode, Listener listener, IApInterface apInterface, INetworkManagementService nms, WifiApConfigStore wifiApConfigStore, WifiConfiguration config, WifiMetrics wifiMetrics) { mStateMachine = new SoftApStateMachine(looper); mWifiNative = wifiNative; mCountryCode = countryCode; mListener = listener; mApInterface = apInterface; mNwService = nms; mWifiApConfigStore = wifiApConfigStore; if (config == null) { mApConfig = mWifiApConfigStore.getApConfiguration(); } else { mApConfig = config; } mWifiMetrics = wifiMetrics; } /** * Start soft AP with the supplied config. */ public void start() { mStateMachine.sendMessage(SoftApStateMachine.CMD_START, mApConfig); } /** * Stop soft AP. */ public void stop() { mStateMachine.sendMessage(SoftApStateMachine.CMD_STOP); } /** * Update AP state. * @param state new AP state * @param reason Failure reason if the new AP state is in failure state */ private void updateApState(int state, int reason) { if (mListener != null) { mListener.onStateChanged(state, reason); } } /** * Start a soft AP instance with the given configuration. * @param config AP configuration * @return integer result code */ private int startSoftAp(WifiConfiguration config) { if (config == null || config.SSID == null) { Log.e(TAG, "Unable to start soft AP without valid configuration"); return ERROR_GENERIC; } // Make a copy of configuration for updating AP band and channel. WifiConfiguration localConfig = new WifiConfiguration(config); int result = ApConfigUtil.updateApChannelConfig( mWifiNative, mCountryCode, mWifiApConfigStore.getAllowed2GChannel(), localConfig); if (result != SUCCESS) { Log.e(TAG, "Failed to update AP band and channel"); return result; } // Setup country code if it is provided. if (mCountryCode != null) { // Country code is mandatory for 5GHz band, return an error if failed to set // country code when AP is configured for 5GHz band. if (!mWifiNative.setCountryCodeHal(mCountryCode.toUpperCase(Locale.ROOT)) && config.apBand == WifiConfiguration.AP_BAND_5GHZ) { Log.e(TAG, "Failed to set country code, required for setting up " + "soft ap in 5GHz"); return ERROR_GENERIC; } } int encryptionType = getIApInterfaceEncryptionType(localConfig); try { // Note that localConfig.SSID is intended to be either a hex string or "double quoted". // However, it seems that whatever is handing us these configurations does not obey // this convention. boolean success = mApInterface.writeHostapdConfig( localConfig.SSID.getBytes(StandardCharsets.UTF_8), false, localConfig.apChannel, encryptionType, (localConfig.preSharedKey != null) ? localConfig.preSharedKey.getBytes(StandardCharsets.UTF_8) : new byte[0]); if (!success) { Log.e(TAG, "Failed to write hostapd configuration"); return ERROR_GENERIC; } success = mApInterface.startHostapd(); if (!success) { Log.e(TAG, "Failed to start hostapd."); return ERROR_GENERIC; } } catch (RemoteException e) { Log.e(TAG, "Exception in starting soft AP: " + e); } Log.d(TAG, "Soft AP is started"); return SUCCESS; } private static int getIApInterfaceEncryptionType(WifiConfiguration localConfig) { int encryptionType; switch (localConfig.getAuthType()) { case KeyMgmt.NONE: encryptionType = IApInterface.ENCRYPTION_TYPE_NONE; break; case KeyMgmt.WPA_PSK: encryptionType = IApInterface.ENCRYPTION_TYPE_WPA; break; case KeyMgmt.WPA2_PSK: encryptionType = IApInterface.ENCRYPTION_TYPE_WPA2; break; default: // We really shouldn't default to None, but this was how NetworkManagementService // used to do this. encryptionType = IApInterface.ENCRYPTION_TYPE_NONE; break; } return encryptionType; } /** * Teardown soft AP. */ private void stopSoftAp() { try { mApInterface.stopHostapd(); } catch (RemoteException e) { Log.e(TAG, "Exception in stopping soft AP: " + e); return; } Log.d(TAG, "Soft AP is stopped"); } private class SoftApStateMachine extends StateMachine { // Commands for the state machine. public static final int CMD_START = 0; public static final int CMD_STOP = 1; public static final int CMD_AP_INTERFACE_BINDER_DEATH = 2; public static final int CMD_INTERFACE_STATUS_CHANGED = 3; private final State mIdleState = new IdleState(); private final State mStartedState = new StartedState(); private final StateMachineDeathRecipient mDeathRecipient = new StateMachineDeathRecipient(this, CMD_AP_INTERFACE_BINDER_DEATH); private NetworkObserver mNetworkObserver; private class NetworkObserver extends BaseNetworkObserver { private final String mIfaceName; NetworkObserver(String ifaceName) { mIfaceName = ifaceName; } @Override public void interfaceLinkStateChanged(String iface, boolean up) { if (mIfaceName.equals(iface)) { SoftApStateMachine.this.sendMessage( CMD_INTERFACE_STATUS_CHANGED, up ? 1 : 0, 0, this); } } } SoftApStateMachine(Looper looper) { super(TAG, looper); addState(mIdleState); addState(mStartedState); setInitialState(mIdleState); start(); } private class IdleState extends State { @Override public void enter() { mDeathRecipient.unlinkToDeath(); unregisterObserver(); } @Override public boolean processMessage(Message message) { switch (message.what) { case CMD_START: updateApState(WifiManager.WIFI_AP_STATE_ENABLING, 0); if (!mDeathRecipient.linkToDeath(mApInterface.asBinder())) { mDeathRecipient.unlinkToDeath(); updateApState(WifiManager.WIFI_AP_STATE_FAILED, WifiManager.SAP_START_FAILURE_GENERAL); mWifiMetrics.incrementSoftApStartResult( false, WifiManager.SAP_START_FAILURE_GENERAL); break; } try { mNetworkObserver = new NetworkObserver(mApInterface.getInterfaceName()); mNwService.registerObserver(mNetworkObserver); } catch (RemoteException e) { mDeathRecipient.unlinkToDeath(); unregisterObserver(); updateApState(WifiManager.WIFI_AP_STATE_FAILED, WifiManager.SAP_START_FAILURE_GENERAL); mWifiMetrics.incrementSoftApStartResult( false, WifiManager.SAP_START_FAILURE_GENERAL); break; } int result = startSoftAp((WifiConfiguration) message.obj); if (result != SUCCESS) { int failureReason = WifiManager.SAP_START_FAILURE_GENERAL; if (result == ERROR_NO_CHANNEL) { failureReason = WifiManager.SAP_START_FAILURE_NO_CHANNEL; } mDeathRecipient.unlinkToDeath(); unregisterObserver(); updateApState(WifiManager.WIFI_AP_STATE_FAILED, failureReason); mWifiMetrics.incrementSoftApStartResult(false, failureReason); break; } transitionTo(mStartedState); break; default: // Ignore all other commands. break; } return HANDLED; } private void unregisterObserver() { if (mNetworkObserver == null) { return; } try { mNwService.unregisterObserver(mNetworkObserver); } catch (RemoteException e) { } mNetworkObserver = null; } } private class StartedState extends State { private boolean mIfaceIsUp; private void onUpChanged(boolean isUp) { if (isUp == mIfaceIsUp) { return; // no change } mIfaceIsUp = isUp; if (isUp) { Log.d(TAG, "SoftAp is ready for use"); updateApState(WifiManager.WIFI_AP_STATE_ENABLED, 0); mWifiMetrics.incrementSoftApStartResult(true, 0); } else { // TODO: handle the case where the interface was up, but goes down } } @Override public void enter() { mIfaceIsUp = false; InterfaceConfiguration config = null; try { config = mNwService.getInterfaceConfig(mApInterface.getInterfaceName()); } catch (RemoteException e) { } if (config != null) { onUpChanged(config.isUp()); } } @Override public boolean processMessage(Message message) { switch (message.what) { case CMD_INTERFACE_STATUS_CHANGED: if (message.obj != mNetworkObserver) { // This is from some time before the most recent configuration. break; } boolean isUp = message.arg1 == 1; onUpChanged(isUp); break; case CMD_START: // Already started, ignore this command. break; case CMD_AP_INTERFACE_BINDER_DEATH: case CMD_STOP: updateApState(WifiManager.WIFI_AP_STATE_DISABLING, 0); stopSoftAp(); if (message.what == CMD_AP_INTERFACE_BINDER_DEATH) { updateApState(WifiManager.WIFI_AP_STATE_FAILED, WifiManager.SAP_START_FAILURE_GENERAL); } else { updateApState(WifiManager.WIFI_AP_STATE_DISABLED, 0); } transitionTo(mIdleState); break; default: return NOT_HANDLED; } return HANDLED; } } } }