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.annotation.NonNull;
20import android.content.Context;
21import android.net.wifi.WifiConfiguration;
22import android.net.wifi.WifiManager;
23import android.os.BatteryStats;
24import android.os.Handler;
25import android.os.Looper;
26import android.os.Message;
27import android.os.RemoteException;
28import android.util.ArraySet;
29import android.util.Log;
30
31import com.android.internal.app.IBatteryStats;
32import com.android.internal.util.Protocol;
33import com.android.internal.util.State;
34import com.android.internal.util.StateMachine;
35import com.android.server.wifi.WifiNative.StatusListener;
36
37import java.io.FileDescriptor;
38import java.io.PrintWriter;
39
40/**
41 * This class provides the implementation for different WiFi operating modes.
42 *
43 * NOTE: The class is a WIP and is in active development.  It is intended to replace the existing
44 * WifiStateMachine.java class when the rearchitecture is complete.
45 */
46public class WifiStateMachinePrime {
47    private static final String TAG = "WifiStateMachinePrime";
48
49    private ModeStateMachine mModeStateMachine;
50
51    // Holder for active mode managers
52    private final ArraySet<ActiveModeManager> mActiveModeManagers;
53    // DefaultModeManager used to service API calls when there are not active mode managers.
54    private DefaultModeManager mDefaultModeManager;
55
56    private final WifiInjector mWifiInjector;
57    private final Context mContext;
58    private final Looper mLooper;
59    private final Handler mHandler;
60    private final WifiNative mWifiNative;
61    private final IBatteryStats mBatteryStats;
62    private final SelfRecovery mSelfRecovery;
63    private BaseWifiDiagnostics mWifiDiagnostics;
64    private final ScanRequestProxy mScanRequestProxy;
65
66    // The base for wifi message types
67    static final int BASE = Protocol.BASE_WIFI;
68
69    // The message identifiers below are mapped to those in WifiStateMachine when applicable.
70    // Start the soft access point
71    static final int CMD_START_AP                                       = BASE + 21;
72    // Indicates soft ap start failed
73    static final int CMD_START_AP_FAILURE                               = BASE + 22;
74    // Stop the soft access point
75    static final int CMD_STOP_AP                                        = BASE + 23;
76    // Soft access point teardown is completed
77    static final int CMD_AP_STOPPED                                     = BASE + 24;
78
79    // Start Scan Only mode
80    static final int CMD_START_SCAN_ONLY_MODE                           = BASE + 200;
81    // Indicates that start Scan only mode failed
82    static final int CMD_START_SCAN_ONLY_MODE_FAILURE                   = BASE + 201;
83    // Indicates that scan only mode stopped
84    static final int CMD_STOP_SCAN_ONLY_MODE                            = BASE + 202;
85    // ScanOnly mode teardown is complete
86    static final int CMD_SCAN_ONLY_MODE_STOPPED                         = BASE + 203;
87    // ScanOnly mode failed
88    static final int CMD_SCAN_ONLY_MODE_FAILED                          = BASE + 204;
89
90    // Start Client mode
91    static final int CMD_START_CLIENT_MODE                              = BASE + 300;
92    // Indicates that start client mode failed
93    static final int CMD_START_CLIENT_MODE_FAILURE                      = BASE + 301;
94    // Indicates that client mode stopped
95    static final int CMD_STOP_CLIENT_MODE                               = BASE + 302;
96    // Client mode teardown is complete
97    static final int CMD_CLIENT_MODE_STOPPED                            = BASE + 303;
98    // Client mode failed
99    static final int CMD_CLIENT_MODE_FAILED                             = BASE + 304;
100
101    private StatusListener mWifiNativeStatusListener;
102
103    private WifiManager.SoftApCallback mSoftApCallback;
104    private ScanOnlyModeManager.Listener mScanOnlyCallback;
105    private ClientModeManager.Listener mClientModeCallback;
106
107    /**
108     * Called from WifiServiceImpl to register a callback for notifications from SoftApManager
109     */
110    public void registerSoftApCallback(@NonNull WifiManager.SoftApCallback callback) {
111        mSoftApCallback = callback;
112    }
113
114    /**
115     * Called from WifiController to register a callback for notifications from ScanOnlyModeManager
116     */
117    public void registerScanOnlyCallback(@NonNull ScanOnlyModeManager.Listener callback) {
118        mScanOnlyCallback = callback;
119    }
120
121    /**
122     * Called from WifiController to register a callback for notifications from ClientModeManager
123     */
124    public void registerClientModeCallback(@NonNull ClientModeManager.Listener callback) {
125        mClientModeCallback = callback;
126    }
127
128    WifiStateMachinePrime(WifiInjector wifiInjector,
129                          Context context,
130                          Looper looper,
131                          WifiNative wifiNative,
132                          DefaultModeManager defaultModeManager,
133                          IBatteryStats batteryStats) {
134        mWifiInjector = wifiInjector;
135        mContext = context;
136        mLooper = looper;
137        mHandler = new Handler(looper);
138        mWifiNative = wifiNative;
139        mActiveModeManagers = new ArraySet();
140        mDefaultModeManager = defaultModeManager;
141        mBatteryStats = batteryStats;
142        mSelfRecovery = mWifiInjector.getSelfRecovery();
143        mWifiDiagnostics = mWifiInjector.getWifiDiagnostics();
144        mScanRequestProxy = mWifiInjector.getScanRequestProxy();
145        mModeStateMachine = new ModeStateMachine();
146        mWifiNativeStatusListener = new WifiNativeStatusListener();
147        mWifiNative.registerStatusListener(mWifiNativeStatusListener);
148    }
149
150    /**
151     * Method to switch wifi into client mode where connections to configured networks will be
152     * attempted.
153     */
154    public void enterClientMode() {
155        changeMode(ModeStateMachine.CMD_START_CLIENT_MODE);
156    }
157
158    /**
159     * Method to switch wifi into scan only mode where network connection attempts will not be made.
160     *
161     * This mode is utilized by location scans.  If wifi is disabled by a user, but they have
162     * previously configured their device to perform location scans, this mode allows wifi to
163     * fulfill the location scan requests but will not be used for connectivity.
164     */
165    public void enterScanOnlyMode() {
166        changeMode(ModeStateMachine.CMD_START_SCAN_ONLY_MODE);
167    }
168
169    /**
170     * Method to enable soft ap for wifi hotspot.
171     *
172     * The supplied SoftApModeConfiguration includes the target softap WifiConfiguration (or null if
173     * the persisted config is to be used) and the target operating mode (ex,
174     * {@link WifiManager.IFACE_IP_MODE_TETHERED} {@link WifiManager.IFACE_IP_MODE_LOCAL_ONLY}).
175     *
176     * @param wifiConfig SoftApModeConfiguration for the hostapd softap
177     */
178    public void enterSoftAPMode(@NonNull SoftApModeConfiguration wifiConfig) {
179        mHandler.post(() -> {
180            startSoftAp(wifiConfig);
181        });
182    }
183
184    /**
185     * Method to stop soft ap for wifi hotspot.
186     *
187     * This method will stop any active softAp mode managers.
188     */
189    public void stopSoftAPMode() {
190        mHandler.post(() -> {
191            for (ActiveModeManager manager : mActiveModeManagers) {
192                if (manager instanceof SoftApManager) {
193                    Log.d(TAG, "Stopping SoftApModeManager");
194                    manager.stop();
195                }
196            }
197            updateBatteryStatsWifiState(false);
198        });
199    }
200
201    /**
202     * Method to disable wifi in sta/client mode scenarios.
203     *
204     * This mode will stop any client/scan modes and will not perform any network scans.
205     */
206    public void disableWifi() {
207        changeMode(ModeStateMachine.CMD_DISABLE_WIFI);
208    }
209
210    /**
211     * Method to stop all active modes, for example, when toggling airplane mode.
212     */
213    public void shutdownWifi() {
214        mHandler.post(() -> {
215            for (ActiveModeManager manager : mActiveModeManagers) {
216                manager.stop();
217            }
218            updateBatteryStatsWifiState(false);
219        });
220    }
221
222    /**
223     * Dump current state for active mode managers.
224     *
225     * Must be called from WifiStateMachine thread.
226     */
227    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
228        pw.println("Dump of " + TAG);
229
230        pw.println("Current wifi mode: " + getCurrentMode());
231        pw.println("NumActiveModeManagers: " + mActiveModeManagers.size());
232        for (ActiveModeManager manager : mActiveModeManagers) {
233            manager.dump(fd, pw, args);
234        }
235    }
236
237    protected String getCurrentMode() {
238        return mModeStateMachine.getCurrentMode();
239    }
240
241    private void changeMode(int newMode) {
242        mModeStateMachine.sendMessage(newMode);
243    }
244
245    /**
246     *  Helper class to wrap the ActiveModeManager callback objects.
247     */
248    private class ModeCallback {
249        ActiveModeManager mActiveManager;
250
251        void setActiveModeManager(ActiveModeManager manager) {
252            mActiveManager = manager;
253        }
254
255        ActiveModeManager getActiveModeManager() {
256            return mActiveManager;
257        }
258    }
259
260    private class ModeStateMachine extends StateMachine {
261        // Commands for the state machine  - these will be removed,
262        // along with the StateMachine itself
263        public static final int CMD_START_CLIENT_MODE    = 0;
264        public static final int CMD_START_SCAN_ONLY_MODE = 1;
265        public static final int CMD_DISABLE_WIFI         = 3;
266
267        private final State mWifiDisabledState = new WifiDisabledState();
268        private final State mClientModeActiveState = new ClientModeActiveState();
269        private final State mScanOnlyModeActiveState = new ScanOnlyModeActiveState();
270
271        ModeStateMachine() {
272            super(TAG, mLooper);
273
274            addState(mClientModeActiveState);
275            addState(mScanOnlyModeActiveState);
276            addState(mWifiDisabledState);
277
278            Log.d(TAG, "Starting Wifi in WifiDisabledState");
279            setInitialState(mWifiDisabledState);
280            start();
281        }
282
283        private String getCurrentMode() {
284            return getCurrentState().getName();
285        }
286
287        private boolean checkForAndHandleModeChange(Message message) {
288            switch(message.what) {
289                case ModeStateMachine.CMD_START_CLIENT_MODE:
290                    Log.d(TAG, "Switching from " + getCurrentMode() + " to ClientMode");
291                    mModeStateMachine.transitionTo(mClientModeActiveState);
292                    break;
293                case ModeStateMachine.CMD_START_SCAN_ONLY_MODE:
294                    Log.d(TAG, "Switching from " + getCurrentMode() + " to ScanOnlyMode");
295                    mModeStateMachine.transitionTo(mScanOnlyModeActiveState);
296                    break;
297                case ModeStateMachine.CMD_DISABLE_WIFI:
298                    Log.d(TAG, "Switching from " + getCurrentMode() + " to WifiDisabled");
299                    mModeStateMachine.transitionTo(mWifiDisabledState);
300                    break;
301                default:
302                    return NOT_HANDLED;
303            }
304            return HANDLED;
305        }
306
307        class ModeActiveState extends State {
308            ActiveModeManager mManager;
309            @Override
310            public boolean processMessage(Message message) {
311                // handle messages for changing modes here
312                return NOT_HANDLED;
313            }
314
315            @Override
316            public void exit() {
317                // Active states must have a mode manager, so this should not be null, but it isn't
318                // obvious from the structure - add a null check here, just in case this is missed
319                // in the future
320                if (mManager != null) {
321                    mManager.stop();
322                    mActiveModeManagers.remove(mManager);
323                }
324                updateBatteryStatsWifiState(false);
325            }
326        }
327
328        class WifiDisabledState extends ModeActiveState {
329            @Override
330            public void enter() {
331                Log.d(TAG, "Entering WifiDisabledState");
332                mDefaultModeManager.sendScanAvailableBroadcast(mContext, false);
333                mScanRequestProxy.enableScanningForHiddenNetworks(false);
334                mScanRequestProxy.clearScanResults();
335            }
336
337            @Override
338            public boolean processMessage(Message message) {
339                Log.d(TAG, "received a message in WifiDisabledState: " + message);
340                if (checkForAndHandleModeChange(message)) {
341                    return HANDLED;
342                }
343                return NOT_HANDLED;
344            }
345
346            @Override
347            public void exit() {
348                // do not have an active mode manager...  nothing to clean up
349            }
350
351        }
352
353        class ClientModeActiveState extends ModeActiveState {
354            ClientListener mListener;
355            private class ClientListener implements ClientModeManager.Listener {
356                @Override
357                public void onStateChanged(int state) {
358                    // make sure this listener is still active
359                    if (this != mListener) {
360                        Log.d(TAG, "Client mode state change from previous manager");
361                        return;
362                    }
363
364                    Log.d(TAG, "State changed from client mode. state = " + state);
365
366                    if (state == WifiManager.WIFI_STATE_UNKNOWN) {
367                        // error while setting up client mode or an unexpected failure.
368                        mModeStateMachine.sendMessage(CMD_CLIENT_MODE_FAILED, this);
369                    } else if (state == WifiManager.WIFI_STATE_DISABLED) {
370                        // client mode stopped
371                        mModeStateMachine.sendMessage(CMD_CLIENT_MODE_STOPPED, this);
372                    } else if (state == WifiManager.WIFI_STATE_ENABLED) {
373                        // client mode is ready to go
374                        Log.d(TAG, "client mode active");
375                    } else {
376                        // only care if client mode stopped or started, dropping
377                    }
378                }
379            }
380
381            @Override
382            public void enter() {
383                Log.d(TAG, "Entering ClientModeActiveState");
384
385                mListener = new ClientListener();
386                mManager = mWifiInjector.makeClientModeManager(mListener);
387                mManager.start();
388                mActiveModeManagers.add(mManager);
389
390                updateBatteryStatsWifiState(true);
391            }
392
393            @Override
394            public void exit() {
395                super.exit();
396                mListener = null;
397            }
398
399            @Override
400            public boolean processMessage(Message message) {
401                if (checkForAndHandleModeChange(message)) {
402                    return HANDLED;
403                }
404
405                switch(message.what) {
406                    case CMD_START_CLIENT_MODE:
407                        Log.d(TAG, "Received CMD_START_CLIENT_MODE when active - drop");
408                        break;
409                    case CMD_CLIENT_MODE_FAILED:
410                        if (mListener != message.obj) {
411                            Log.d(TAG, "Client mode state change from previous manager");
412                            return HANDLED;
413                        }
414                        Log.d(TAG, "ClientMode failed, return to WifiDisabledState.");
415                        // notify WifiController that ClientMode failed
416                        mClientModeCallback.onStateChanged(WifiManager.WIFI_STATE_UNKNOWN);
417                        mModeStateMachine.transitionTo(mWifiDisabledState);
418                        break;
419                    case CMD_CLIENT_MODE_STOPPED:
420                        if (mListener != message.obj) {
421                            Log.d(TAG, "Client mode state change from previous manager");
422                            return HANDLED;
423                        }
424
425                        Log.d(TAG, "ClientMode stopped, return to WifiDisabledState.");
426                        // notify WifiController that ClientMode stopped
427                        mClientModeCallback.onStateChanged(WifiManager.WIFI_STATE_DISABLED);
428                        mModeStateMachine.transitionTo(mWifiDisabledState);
429                        break;
430                    default:
431                        return NOT_HANDLED;
432                }
433                return NOT_HANDLED;
434            }
435        }
436
437        class ScanOnlyModeActiveState extends ModeActiveState {
438            ScanOnlyListener mListener;
439            private class ScanOnlyListener implements ScanOnlyModeManager.Listener {
440                @Override
441                public void onStateChanged(int state) {
442                    if (this != mListener) {
443                        Log.d(TAG, "ScanOnly mode state change from previous manager");
444                        return;
445                    }
446
447                    if (state == WifiManager.WIFI_STATE_UNKNOWN) {
448                        Log.d(TAG, "ScanOnlyMode mode failed");
449                        // error while setting up scan mode or an unexpected failure.
450                        mModeStateMachine.sendMessage(CMD_SCAN_ONLY_MODE_FAILED, this);
451                    } else if (state == WifiManager.WIFI_STATE_DISABLED) {
452                        Log.d(TAG, "ScanOnlyMode stopped");
453                        //scan only mode stopped
454                        mModeStateMachine.sendMessage(CMD_SCAN_ONLY_MODE_STOPPED, this);
455                    } else if (state == WifiManager.WIFI_STATE_ENABLED) {
456                        // scan mode is ready to go
457                        Log.d(TAG, "scan mode active");
458                    } else {
459                        Log.d(TAG, "unexpected state update: " + state);
460                    }
461                }
462            }
463
464            @Override
465            public void enter() {
466                Log.d(TAG, "Entering ScanOnlyModeActiveState");
467
468                mListener = new ScanOnlyListener();
469                mManager = mWifiInjector.makeScanOnlyModeManager(mListener);
470                mManager.start();
471                mActiveModeManagers.add(mManager);
472
473                updateBatteryStatsWifiState(true);
474                updateBatteryStatsScanModeActive();
475            }
476
477            @Override
478            public void exit() {
479                super.exit();
480                mListener = null;
481            }
482
483            @Override
484            public boolean processMessage(Message message) {
485                if (checkForAndHandleModeChange(message)) {
486                    return HANDLED;
487                }
488
489                switch(message.what) {
490                    case CMD_START_SCAN_ONLY_MODE:
491                        Log.d(TAG, "Received CMD_START_SCAN_ONLY_MODE when active - drop");
492                        break;
493                    case CMD_SCAN_ONLY_MODE_FAILED:
494                        if (mListener != message.obj) {
495                            Log.d(TAG, "ScanOnly mode state change from previous manager");
496                            return HANDLED;
497                        }
498
499                        Log.d(TAG, "ScanOnlyMode failed, return to WifiDisabledState.");
500                        // notify WifiController that ScanOnlyMode failed
501                        mScanOnlyCallback.onStateChanged(WifiManager.WIFI_STATE_UNKNOWN);
502                        mModeStateMachine.transitionTo(mWifiDisabledState);
503                        break;
504                    case CMD_SCAN_ONLY_MODE_STOPPED:
505                        if (mListener != message.obj) {
506                            Log.d(TAG, "ScanOnly mode state change from previous manager");
507                            return HANDLED;
508                        }
509
510                        Log.d(TAG, "ScanOnlyMode stopped, return to WifiDisabledState.");
511                        // notify WifiController that ScanOnlyMode stopped
512                        mScanOnlyCallback.onStateChanged(WifiManager.WIFI_STATE_DISABLED);
513                        mModeStateMachine.transitionTo(mWifiDisabledState);
514                        break;
515                    default:
516                        return NOT_HANDLED;
517                }
518                return HANDLED;
519            }
520        }
521    }  // class ModeStateMachine
522
523    private class SoftApCallbackImpl extends ModeCallback implements WifiManager.SoftApCallback {
524        @Override
525        public void onStateChanged(int state, int reason) {
526            if (state == WifiManager.WIFI_AP_STATE_DISABLED) {
527                mActiveModeManagers.remove(getActiveModeManager());
528                updateBatteryStatsWifiState(false);
529            } else if (state == WifiManager.WIFI_AP_STATE_FAILED) {
530                mActiveModeManagers.remove(getActiveModeManager());
531                updateBatteryStatsWifiState(false);
532            }
533
534            if (mSoftApCallback != null) {
535                mSoftApCallback.onStateChanged(state, reason);
536            }
537        }
538
539        @Override
540        public void onNumClientsChanged(int numClients) {
541            if (mSoftApCallback != null) {
542                mSoftApCallback.onNumClientsChanged(numClients);
543            } else {
544                Log.d(TAG, "SoftApCallback is null. Dropping NumClientsChanged event.");
545            }
546        }
547    }
548
549    private void startSoftAp(SoftApModeConfiguration softapConfig) {
550        Log.d(TAG, "Starting SoftApModeManager");
551
552        WifiConfiguration config = softapConfig.getWifiConfiguration();
553        if (config != null && config.SSID != null) {
554            Log.d(TAG, "Passing config to SoftApManager! " + config);
555        } else {
556            config = null;
557        }
558
559        SoftApCallbackImpl callback = new SoftApCallbackImpl();
560        ActiveModeManager manager = mWifiInjector.makeSoftApManager(callback, softapConfig);
561        callback.setActiveModeManager(manager);
562        manager.start();
563        mActiveModeManagers.add(manager);
564        updateBatteryStatsWifiState(true);
565    }
566
567    /**
568     *  Helper method to report wifi state as on/off (doesn't matter which mode).
569     *
570     *  @param enabled boolean indicating that some mode has been turned on or off
571     */
572    private void updateBatteryStatsWifiState(boolean enabled) {
573        try {
574            if (enabled) {
575                if (mActiveModeManagers.size() == 1) {
576                    // only report wifi on if we haven't already
577                    mBatteryStats.noteWifiOn();
578                }
579            } else {
580                if (mActiveModeManagers.size() == 0) {
581                    // only report if we don't have any active modes
582                    mBatteryStats.noteWifiOff();
583                }
584            }
585        } catch (RemoteException e) {
586            Log.e(TAG, "Failed to note battery stats in wifi");
587        }
588    }
589
590    private void updateBatteryStatsScanModeActive() {
591        try {
592            mBatteryStats.noteWifiState(BatteryStats.WIFI_STATE_OFF_SCANNING, null);
593        } catch (RemoteException e) {
594            Log.e(TAG, "Failed to note battery stats in wifi");
595        }
596    }
597
598    // callback used to receive callbacks about underlying native failures
599    private final class WifiNativeStatusListener implements StatusListener {
600
601        @Override
602        public void onStatusChanged(boolean isReady) {
603            if (!isReady) {
604                mHandler.post(() -> {
605                    Log.e(TAG, "One of the native daemons died. Triggering recovery");
606                    mWifiDiagnostics.captureBugReportData(
607                            WifiDiagnostics.REPORT_REASON_WIFINATIVE_FAILURE);
608
609                    // immediately trigger SelfRecovery if we receive a notice about an
610                    // underlying daemon failure
611                    mWifiInjector.getSelfRecovery().trigger(SelfRecovery.REASON_WIFINATIVE_FAILURE);
612                });
613            }
614        }
615    };
616}
617