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