SoftApManager.java revision ef8f064681d7e3b92a0c3f2fdaa55a656e68486d
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.net.wifi.IApInterface;
24import android.net.wifi.WifiConfiguration;
25import android.net.wifi.WifiManager;
26import android.net.wifi.WifiConfiguration.KeyMgmt;
27import android.os.IBinder.DeathRecipient;
28import android.os.Looper;
29import android.os.Message;
30import android.os.RemoteException;
31import android.util.Log;
32
33import com.android.internal.util.State;
34import com.android.internal.util.StateMachine;
35import com.android.server.wifi.util.ApConfigUtil;
36
37import java.nio.charset.StandardCharsets;
38import java.util.ArrayList;
39import java.util.Locale;
40
41/**
42 * Manage WiFi in AP mode.
43 * The internal state machine runs under "WifiStateMachine" thread context.
44 */
45public class SoftApManager {
46    private static final String TAG = "SoftApManager";
47
48    private final WifiNative mWifiNative;
49    private final ArrayList<Integer> mAllowed2GChannels;
50
51    private final String mCountryCode;
52
53    private final SoftApStateMachine mStateMachine;
54
55    private final Listener mListener;
56
57    private final IApInterface mApInterface;
58
59    /**
60     * Listener for soft AP state changes.
61     */
62    public interface Listener {
63        /**
64         * Invoke when AP state changed.
65         * @param state new AP state
66         * @param failureReason reason when in failed state
67         */
68        void onStateChanged(int state, int failureReason);
69    }
70
71    public SoftApManager(Looper looper,
72                         WifiNative wifiNative,
73                         String countryCode,
74                         ArrayList<Integer> allowed2GChannels,
75                         Listener listener,
76                         IApInterface apInterface) {
77        mStateMachine = new SoftApStateMachine(looper);
78
79        mWifiNative = wifiNative;
80        mCountryCode = countryCode;
81        mAllowed2GChannels = allowed2GChannels;
82        mListener = listener;
83        mApInterface = apInterface;
84    }
85
86    /**
87     * Start soft AP with given configuration.
88     * @param config AP configuration
89     */
90    public void start(WifiConfiguration config) {
91        mStateMachine.sendMessage(SoftApStateMachine.CMD_START, config);
92    }
93
94    /**
95     * Stop soft AP.
96     */
97    public void stop() {
98        mStateMachine.sendMessage(SoftApStateMachine.CMD_STOP);
99    }
100
101    /**
102     * Update AP state.
103     * @param state new AP state
104     * @param reason Failure reason if the new AP state is in failure state
105     */
106    private void updateApState(int state, int reason) {
107        if (mListener != null) {
108            mListener.onStateChanged(state, reason);
109        }
110    }
111
112    /**
113     * Start a soft AP instance with the given configuration.
114     * @param config AP configuration
115     * @return integer result code
116     */
117    private int startSoftAp(WifiConfiguration config) {
118        if (config == null || config.SSID == null) {
119            Log.e(TAG, "Unable to start soft AP without valid configuration");
120            return ERROR_GENERIC;
121        }
122
123        /* Make a copy of configuration for updating AP band and channel. */
124        WifiConfiguration localConfig = new WifiConfiguration(config);
125
126        int result = ApConfigUtil.updateApChannelConfig(
127                mWifiNative, mCountryCode, mAllowed2GChannels, localConfig);
128        if (result != SUCCESS) {
129            Log.e(TAG, "Failed to update AP band and channel");
130            return result;
131        }
132
133        /* Setup country code if it is provide. */
134        if (mCountryCode != null) {
135            /**
136             * Country code is mandatory for 5GHz band, return an error if failed to set
137             * country code when AP is configured for 5GHz band.
138             */
139            if (!mWifiNative.setCountryCodeHal(mCountryCode.toUpperCase(Locale.ROOT))
140                    && config.apBand == WifiConfiguration.AP_BAND_5GHZ) {
141                Log.e(TAG, "Failed to set country code, required for setting up "
142                        + "soft ap in 5GHz");
143                return ERROR_GENERIC;
144            }
145        }
146
147        int encryptionType = getIApInterfaceEncryptionType(localConfig);
148
149        try {
150            // Note that localConfig.SSID is intended to be either a hex string or "double quoted".
151            // However, it seems that whatever is handing us these configurations does not obey
152            // this convention.
153            boolean success = mApInterface.writeHostapdConfig(
154                    localConfig.SSID.getBytes(StandardCharsets.UTF_8), false,
155                    localConfig.apChannel, encryptionType,
156                    (localConfig.preSharedKey != null)
157                            ? localConfig.preSharedKey.getBytes(StandardCharsets.UTF_8)
158                            : new byte[0]);
159            if (!success) {
160                Log.e(TAG, "Failed to write hostapd configuration");
161                return ERROR_GENERIC;
162            }
163
164            success = mApInterface.startHostapd();
165            if (!success) {
166                Log.e(TAG, "Failed to start hostapd.");
167                return ERROR_GENERIC;
168            }
169        } catch (RemoteException e) {
170            Log.e(TAG, "Exception in starting soft AP: " + e);
171        }
172
173        Log.d(TAG, "Soft AP is started");
174
175        return SUCCESS;
176    }
177
178    private static int getIApInterfaceEncryptionType(WifiConfiguration localConfig) {
179        int encryptionType;
180        switch (localConfig.getAuthType()) {
181            case KeyMgmt.NONE:
182                encryptionType = IApInterface.ENCRYPTION_TYPE_NONE;
183                break;
184            case KeyMgmt.WPA_PSK:
185                encryptionType = IApInterface.ENCRYPTION_TYPE_WPA;
186                break;
187            case KeyMgmt.WPA2_PSK:
188                encryptionType = IApInterface.ENCRYPTION_TYPE_WPA2;
189                break;
190            default:
191                // We really shouldn't default to None, but this was how NetworkManagementService
192                // used to do this.
193                encryptionType = IApInterface.ENCRYPTION_TYPE_NONE;
194                break;
195        }
196        return encryptionType;
197    }
198
199    /**
200     * Teardown soft AP.
201     */
202    private void stopSoftAp() {
203        try {
204            mApInterface.stopHostapd();
205        } catch (RemoteException e) {
206            Log.e(TAG, "Exception in stopping soft AP: " + e);
207            return;
208        }
209        Log.d(TAG, "Soft AP is stopped");
210    }
211
212    private class SoftApStateMachine extends StateMachine {
213        /* Commands for the state machine. */
214        public static final int CMD_START = 0;
215        public static final int CMD_STOP = 1;
216        public static final int CMD_AP_INTERFACE_BINDER_DEATH = 2;
217
218        private final State mIdleState = new IdleState();
219        private final State mStartedState = new StartedState();
220
221        private final StateMachineDeathRecipient mDeathRecipient =
222                new StateMachineDeathRecipient(this, CMD_AP_INTERFACE_BINDER_DEATH);
223
224        SoftApStateMachine(Looper looper) {
225            super(TAG, looper);
226
227            addState(mIdleState);
228            addState(mStartedState);
229
230            setInitialState(mIdleState);
231            start();
232        }
233
234        private class IdleState extends State {
235            @Override
236            public void enter() {
237                mDeathRecipient.unlinkToDeath();
238            }
239
240            @Override
241            public boolean processMessage(Message message) {
242                switch (message.what) {
243                    case CMD_START:
244                        updateApState(WifiManager.WIFI_AP_STATE_ENABLING, 0);
245                        if (!mDeathRecipient.linkToDeath(mApInterface.asBinder())) {
246                            mDeathRecipient.unlinkToDeath();
247                            updateApState(WifiManager.WIFI_AP_STATE_FAILED,
248                                    WifiManager.SAP_START_FAILURE_GENERAL);
249                            break;
250                        }
251
252                        int result = startSoftAp((WifiConfiguration) message.obj);
253                        if (result != SUCCESS) {
254                            int failureReason = WifiManager.SAP_START_FAILURE_GENERAL;
255                            if (result == ERROR_NO_CHANNEL) {
256                                failureReason = WifiManager.SAP_START_FAILURE_NO_CHANNEL;
257                            }
258                            mDeathRecipient.unlinkToDeath();
259                            updateApState(WifiManager.WIFI_AP_STATE_FAILED, failureReason);
260                            break;
261                        }
262
263                        updateApState(WifiManager.WIFI_AP_STATE_ENABLED, 0);
264                        transitionTo(mStartedState);
265                        break;
266                    default:
267                        /* Ignore all other commands. */
268                        break;
269                }
270
271                return HANDLED;
272            }
273        }
274
275        private class StartedState extends State {
276            @Override
277            public boolean processMessage(Message message) {
278                switch (message.what) {
279                    case CMD_START:
280                        /* Already started, ignore this command. */
281                        break;
282                    case CMD_AP_INTERFACE_BINDER_DEATH:
283                    case CMD_STOP:
284                        updateApState(WifiManager.WIFI_AP_STATE_DISABLING, 0);
285                        stopSoftAp();
286                        if (message.what == CMD_AP_INTERFACE_BINDER_DEATH) {
287                            updateApState(WifiManager.WIFI_AP_STATE_FAILED,
288                                    WifiManager.SAP_START_FAILURE_GENERAL);
289                        } else {
290                            updateApState(WifiManager.WIFI_AP_STATE_DISABLED, 0);
291                        }
292                        transitionTo(mIdleState);
293                        break;
294                    default:
295                        return NOT_HANDLED;
296                }
297                return HANDLED;
298            }
299        }
300
301    }
302}
303