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