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 android.net.wifi.IApInterface;
20import android.net.wifi.IWificond;
21import android.net.wifi.WifiConfiguration;
22import android.net.wifi.WifiManager;
23import android.os.INetworkManagementService;
24import android.os.Looper;
25import android.os.Message;
26import android.os.RemoteException;
27import android.util.Log;
28
29import com.android.internal.util.Protocol;
30import com.android.internal.util.State;
31import com.android.internal.util.StateMachine;
32
33import java.util.Queue;
34import java.util.concurrent.ConcurrentLinkedQueue;
35
36/**
37 * This class provides the implementation for different WiFi operating modes.
38 *
39 * NOTE: The class is a WIP and is in active development.  It is intended to replace the existing
40 * WifiStateMachine.java class when the rearchitecture is complete.
41 */
42public class WifiStateMachinePrime {
43    private static final String TAG = "WifiStateMachinePrime";
44
45    private ModeStateMachine mModeStateMachine;
46
47    private final WifiInjector mWifiInjector;
48    private final Looper mLooper;
49    private final INetworkManagementService mNMService;
50
51    private IWificond mWificond;
52
53    private Queue<WifiConfiguration> mApConfigQueue = new ConcurrentLinkedQueue<>();
54
55    /* The base for wifi message types */
56    static final int BASE = Protocol.BASE_WIFI;
57
58    /* Start the soft access point */
59    static final int CMD_START_AP                                       = BASE + 21;
60    /* Indicates soft ap start failed */
61    static final int CMD_START_AP_FAILURE                               = BASE + 22;
62    /* Stop the soft access point */
63    static final int CMD_STOP_AP                                        = BASE + 23;
64    /* Soft access point teardown is completed. */
65    static final int CMD_AP_STOPPED                                     = BASE + 24;
66
67    WifiStateMachinePrime(WifiInjector wifiInjector,
68                          Looper looper,
69                          INetworkManagementService nmService) {
70        mWifiInjector = wifiInjector;
71        mLooper = looper;
72        mNMService = nmService;
73
74        // Clean up existing interfaces in wificond.
75        // This ensures that the framework and wificond are in a consistent state after a framework
76        // restart.
77        try {
78            mWificond = mWifiInjector.makeWificond();
79            if (mWificond != null) {
80                mWificond.tearDownInterfaces();
81            }
82        } catch (RemoteException e) {
83            Log.e(TAG, "wificond died during framework startup");
84        }
85    }
86
87    /**
88     * Method to switch wifi into client mode where connections to configured networks will be
89     * attempted.
90     */
91    public void enterClientMode() {
92        changeMode(ModeStateMachine.CMD_START_CLIENT_MODE);
93    }
94
95    /**
96     * Method to switch wifi into scan only mode where network connection attempts will not be made.
97     *
98     * This mode is utilized by location scans.  If wifi is disabled by a user, but they have
99     * previously configured their device to perform location scans, this mode allows wifi to
100     * fulfill the location scan requests but will not be used for connectivity.
101     */
102    public void enterScanOnlyMode() {
103        changeMode(ModeStateMachine.CMD_START_SCAN_ONLY_MODE);
104    }
105
106    /**
107     * Method to enable soft ap for wifi hotspot.
108     *
109     * The WifiConfiguration is generally going to be null to indicate that the
110     * currently saved config in WifiApConfigManager should be used.  When the config is
111     * not null, it will be saved in the WifiApConfigManager. This save is performed in the
112     * constructor of SoftApManager.
113     *
114     * @param wifiConfig WifiConfiguration for the hostapd softap
115     */
116    public void enterSoftAPMode(WifiConfiguration wifiConfig) {
117        if (wifiConfig == null) {
118            wifiConfig = new WifiConfiguration();
119        }
120        mApConfigQueue.offer(wifiConfig);
121        changeMode(ModeStateMachine.CMD_START_SOFT_AP_MODE);
122    }
123
124    /**
125     * Method to fully disable wifi.
126     *
127     * This mode will completely shut down wifi and will not perform any network scans.
128     */
129    public void disableWifi() {
130        changeMode(ModeStateMachine.CMD_DISABLE_WIFI);
131    }
132
133    protected String getCurrentMode() {
134        if (mModeStateMachine != null) {
135            return mModeStateMachine.getCurrentMode();
136        }
137        return "WifiDisabledState";
138    }
139
140    private void changeMode(int newMode) {
141        if (mModeStateMachine == null) {
142            if (newMode == ModeStateMachine.CMD_DISABLE_WIFI) {
143                // command is to disable wifi, but it is already disabled.
144                Log.e(TAG, "Received call to disable wifi when it is already disabled.");
145                return;
146            }
147            // state machine was not initialized yet, we must be starting up.
148            mModeStateMachine = new ModeStateMachine();
149        }
150        mModeStateMachine.sendMessage(newMode);
151    }
152
153    private class ModeStateMachine extends StateMachine {
154        // Commands for the state machine.
155        public static final int CMD_START_CLIENT_MODE    = 0;
156        public static final int CMD_START_SCAN_ONLY_MODE = 1;
157        public static final int CMD_START_SOFT_AP_MODE   = 2;
158        public static final int CMD_DISABLE_WIFI         = 3;
159
160        // Create the base modes for WSM.
161        private final State mClientModeState = new ClientModeState();
162        private final State mScanOnlyModeState = new ScanOnlyModeState();
163        private final State mSoftAPModeState = new SoftAPModeState();
164        private final State mWifiDisabledState = new WifiDisabledState();
165
166        // Create the active versions of the modes for WSM.
167        private final State mClientModeActiveState = new ClientModeActiveState();
168        private final State mScanOnlyModeActiveState = new ScanOnlyModeActiveState();
169        private final State mSoftAPModeActiveState = new SoftAPModeActiveState();
170
171        ModeStateMachine() {
172            super(TAG, mLooper);
173
174            // CHECKSTYLE:OFF IndentationCheck
175            addState(mClientModeState);
176              addState(mClientModeActiveState, mClientModeState);
177            addState(mScanOnlyModeState);
178              addState(mScanOnlyModeActiveState, mScanOnlyModeState);
179            addState(mSoftAPModeState);
180              addState(mSoftAPModeActiveState, mSoftAPModeState);
181            addState(mWifiDisabledState);
182            // CHECKSTYLE:ON IndentationCheck
183
184            Log.d(TAG, "Starting Wifi in WifiDisabledState");
185            setInitialState(mWifiDisabledState);
186            start();
187        }
188
189        private String getCurrentMode() {
190            return getCurrentState().getName();
191        }
192
193        private boolean checkForAndHandleModeChange(Message message) {
194            switch(message.what) {
195                case ModeStateMachine.CMD_START_CLIENT_MODE:
196                    Log.d(TAG, "Switching from " + getCurrentMode() + " to ClientMode");
197                    mModeStateMachine.transitionTo(mClientModeState);
198                    break;
199                case ModeStateMachine.CMD_START_SCAN_ONLY_MODE:
200                    Log.d(TAG, "Switching from " + getCurrentMode() + " to ScanOnlyMode");
201                    mModeStateMachine.transitionTo(mScanOnlyModeState);
202                    break;
203                case ModeStateMachine.CMD_START_SOFT_AP_MODE:
204                    Log.d(TAG, "Switching from " + getCurrentMode() + " to SoftApMode");
205                    mModeStateMachine.transitionTo(mSoftAPModeState);
206                    break;
207                case ModeStateMachine.CMD_DISABLE_WIFI:
208                    Log.d(TAG, "Switching from " + getCurrentMode() + " to WifiDisabled");
209                    mModeStateMachine.transitionTo(mWifiDisabledState);
210                    break;
211                default:
212                    return NOT_HANDLED;
213            }
214            return HANDLED;
215        }
216
217        private void tearDownInterfaces() {
218            if (mWificond != null) {
219                try {
220                    mWificond.tearDownInterfaces();
221                } catch (RemoteException e) {
222                    // There is very little we can do here
223                    Log.e(TAG, "Failed to tear down interfaces via wificond");
224                }
225                mWificond = null;
226            }
227            return;
228        }
229
230        class ClientModeState extends State {
231            @Override
232            public void enter() {
233                mWificond = mWifiInjector.makeWificond();
234            }
235
236            @Override
237            public boolean processMessage(Message message) {
238                if (checkForAndHandleModeChange(message)) {
239                    return HANDLED;
240                }
241                return NOT_HANDLED;
242            }
243
244            @Override
245            public void exit() {
246                tearDownInterfaces();
247            }
248        }
249
250        class ScanOnlyModeState extends State {
251            @Override
252            public void enter() {
253            }
254
255            @Override
256            public boolean processMessage(Message message) {
257                if (checkForAndHandleModeChange(message)) {
258                    return HANDLED;
259                }
260                return NOT_HANDLED;
261            }
262
263            @Override
264            public void exit() {
265                // Do not tear down interfaces yet since this mode is not actively controlled or
266                // used in tests at this time.
267                // tearDownInterfaces();
268            }
269        }
270
271        class SoftAPModeState extends State {
272            IApInterface mApInterface = null;
273
274            @Override
275            public void enter() {
276                final Message message = mModeStateMachine.getCurrentMessage();
277                if (message.what != ModeStateMachine.CMD_START_SOFT_AP_MODE) {
278                    Log.d(TAG, "Entering SoftAPMode (idle)");
279                    return;
280                }
281
282                // Continue with setup since we are changing modes
283                mApInterface = null;
284                mWificond = mWifiInjector.makeWificond();
285                if (mWificond == null) {
286                    Log.e(TAG, "Failed to get reference to wificond");
287                    writeApConfigDueToStartFailure();
288                    mModeStateMachine.sendMessage(CMD_START_AP_FAILURE);
289                    return;
290                }
291
292                try {
293                    mApInterface = mWificond.createApInterface();
294                } catch (RemoteException e1) { }
295
296                if (mApInterface == null) {
297                    Log.e(TAG, "Could not get IApInterface instance from wificond");
298                    writeApConfigDueToStartFailure();
299                    mModeStateMachine.sendMessage(CMD_START_AP_FAILURE);
300                    return;
301                }
302                mModeStateMachine.transitionTo(mSoftAPModeActiveState);
303            }
304
305            @Override
306            public boolean processMessage(Message message) {
307                if (checkForAndHandleModeChange(message)) {
308                    return HANDLED;
309                }
310
311                switch(message.what) {
312                    case CMD_START_AP:
313                        Log.d(TAG, "Received CMD_START_AP (now invalid message) - dropping");
314                        break;
315                    case CMD_STOP_AP:
316                        // not in active state, nothing to stop.
317                        break;
318                    case CMD_START_AP_FAILURE:
319                        Log.e(TAG, "Failed to start SoftApMode.  Wait for next mode command.");
320                        break;
321                    case CMD_AP_STOPPED:
322                        Log.d(TAG, "SoftApModeActiveState stopped.  Wait for next mode command.");
323                        break;
324                    default:
325                        return NOT_HANDLED;
326                }
327                return HANDLED;
328            }
329
330            @Override
331            public void exit() {
332                tearDownInterfaces();
333            }
334
335            protected IApInterface getInterface() {
336                return mApInterface;
337            }
338
339            private void writeApConfigDueToStartFailure() {
340                WifiConfiguration config = mApConfigQueue.poll();
341                if (config != null && config.SSID != null) {
342                    // Save valid configs for future calls.
343                    mWifiInjector.getWifiApConfigStore().setApConfiguration(config);
344                }
345            }
346        }
347
348        class WifiDisabledState extends State {
349            @Override
350            public void enter() {
351                // make sure everything is torn down
352                Log.d(TAG, "Entering WifiDisabledState");
353            }
354
355            @Override
356            public boolean processMessage(Message message) {
357                Log.d(TAG, "received a message in WifiDisabledState: " + message);
358                if (checkForAndHandleModeChange(message)) {
359                    return HANDLED;
360                }
361                return NOT_HANDLED;
362            }
363
364        }
365
366        class ModeActiveState extends State {
367            ActiveModeManager mActiveModeManager;
368
369            @Override
370            public boolean processMessage(Message message) {
371                // handle messages for changing modes here
372                return NOT_HANDLED;
373            }
374
375            @Override
376            public void exit() {
377                // clean up objects from an active state - check with mode handlers to make sure
378                // they are stopping properly.
379                mActiveModeManager.stop();
380            }
381        }
382
383        class ClientModeActiveState extends ModeActiveState {
384            @Override
385            public void enter() {
386                this.mActiveModeManager = new ClientModeManager();
387            }
388        }
389
390        class ScanOnlyModeActiveState extends ModeActiveState {
391            @Override
392            public void enter() {
393                this.mActiveModeManager = new ScanOnlyModeManager();
394            }
395        }
396
397        class SoftAPModeActiveState extends ModeActiveState {
398            private class SoftApListener implements SoftApManager.Listener {
399                @Override
400                public void onStateChanged(int state, int reason) {
401                    if (state == WifiManager.WIFI_AP_STATE_DISABLED) {
402                        mModeStateMachine.sendMessage(CMD_AP_STOPPED);
403                    } else if (state == WifiManager.WIFI_AP_STATE_FAILED) {
404                        mModeStateMachine.sendMessage(CMD_START_AP_FAILURE);
405                    }
406                }
407            }
408
409            @Override
410            public void enter() {
411                Log.d(TAG, "Entering SoftApModeActiveState");
412                WifiConfiguration config = mApConfigQueue.poll();
413                if (config != null && config.SSID != null) {
414                    Log.d(TAG, "Passing config to SoftApManager! " + config);
415                } else {
416                    config = null;
417                }
418
419                this.mActiveModeManager = mWifiInjector.makeSoftApManager(mNMService,
420                        new SoftApListener(), ((SoftAPModeState) mSoftAPModeState).getInterface(),
421                        config);
422                mActiveModeManager.start();
423            }
424
425            @Override
426            public boolean processMessage(Message message) {
427                switch(message.what) {
428                    case CMD_START_AP:
429                        Log.d(TAG, "Received CMD_START_AP when active - invalid message - drop");
430                        break;
431                    case CMD_STOP_AP:
432                        mActiveModeManager.stop();
433                        break;
434                    case CMD_START_AP_FAILURE:
435                        Log.d(TAG, "Failed to start SoftApMode.  Return to SoftApMode (inactive).");
436                        mModeStateMachine.transitionTo(mSoftAPModeState);
437                        break;
438                    case CMD_AP_STOPPED:
439                        Log.d(TAG, "SoftApModeActiveState stopped."
440                                + "  Return to SoftApMode (inactive).");
441                        mModeStateMachine.transitionTo(mSoftAPModeState);
442                        break;
443                    default:
444                        return NOT_HANDLED;
445                }
446                return HANDLED;
447            }
448        }
449    }  // class ModeStateMachine
450}
451