1/*
2 * Copyright (C) 2013 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.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.location.LocationManager;
24import android.net.ConnectivityManager;
25import android.net.NetworkInfo;
26import android.net.wifi.WifiManager;
27import android.os.Handler;
28import android.os.Looper;
29import android.os.Message;
30import android.os.SystemClock;
31import android.os.WorkSource;
32import android.provider.Settings;
33import android.util.Log;
34
35import com.android.internal.util.Protocol;
36import com.android.internal.util.State;
37import com.android.internal.util.StateMachine;
38
39/**
40 * WifiController is the class used to manage on/off state of WifiStateMachine for various operating
41 * modes (normal, airplane, wifi hotspot, etc.).
42 */
43public class WifiController extends StateMachine {
44    private static final String TAG = "WifiController";
45    private static final boolean DBG = false;
46    private Context mContext;
47    private boolean mFirstUserSignOnSeen = false;
48
49    /**
50     * See {@link Settings.Global#WIFI_REENABLE_DELAY_MS}.  This is the default value if a
51     * Settings.Global value is not present.  This is the minimum time after wifi is disabled
52     * we'll act on an enable.  Enable requests received before this delay will be deferred.
53     */
54    private static final long DEFAULT_REENABLE_DELAY_MS = 500;
55
56    // finding that delayed messages can sometimes be delivered earlier than expected
57    // probably rounding errors.  add a margin to prevent problems
58    private static final long DEFER_MARGIN_MS = 5;
59
60    NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
61
62    /* References to values tracked in WifiService */
63    private final WifiStateMachine mWifiStateMachine;
64    private final Looper mWifiStateMachineLooper;
65    private final WifiStateMachinePrime mWifiStateMachinePrime;
66    private final WifiSettingsStore mSettingsStore;
67
68    /**
69     * Temporary for computing UIDS that are responsible for starting WIFI.
70     * Protected by mWifiStateTracker lock.
71     */
72    private final WorkSource mTmpWorkSource = new WorkSource();
73
74    private long mReEnableDelayMillis;
75
76    private FrameworkFacade mFacade;
77
78    private static final int BASE = Protocol.BASE_WIFI_CONTROLLER;
79
80    static final int CMD_EMERGENCY_MODE_CHANGED                 = BASE + 1;
81    static final int CMD_SCAN_ALWAYS_MODE_CHANGED               = BASE + 7;
82    static final int CMD_WIFI_TOGGLED                           = BASE + 8;
83    static final int CMD_AIRPLANE_TOGGLED                       = BASE + 9;
84    static final int CMD_SET_AP                                 = BASE + 10;
85    static final int CMD_DEFERRED_TOGGLE                        = BASE + 11;
86    static final int CMD_USER_PRESENT                           = BASE + 12;
87    static final int CMD_AP_START_FAILURE                       = BASE + 13;
88    static final int CMD_EMERGENCY_CALL_STATE_CHANGED           = BASE + 14;
89    static final int CMD_AP_STOPPED                             = BASE + 15;
90    static final int CMD_STA_START_FAILURE                      = BASE + 16;
91    // Command used to trigger a wifi stack restart when in active mode
92    static final int CMD_RECOVERY_RESTART_WIFI                  = BASE + 17;
93    // Internal command used to complete wifi stack restart
94    private static final int CMD_RECOVERY_RESTART_WIFI_CONTINUE = BASE + 18;
95    // Command to disable wifi when SelfRecovery is throttled or otherwise not doing full recovery
96    static final int CMD_RECOVERY_DISABLE_WIFI                  = BASE + 19;
97    static final int CMD_STA_STOPPED                            = BASE + 20;
98    static final int CMD_SCANNING_STOPPED                       = BASE + 21;
99
100    private DefaultState mDefaultState = new DefaultState();
101    private StaEnabledState mStaEnabledState = new StaEnabledState();
102    private StaDisabledState mStaDisabledState = new StaDisabledState();
103    private StaDisabledWithScanState mStaDisabledWithScanState = new StaDisabledWithScanState();
104    private DeviceActiveState mDeviceActiveState = new DeviceActiveState();
105    private EcmState mEcmState = new EcmState();
106
107    private ScanOnlyModeManager.Listener mScanOnlyModeCallback = new ScanOnlyCallback();
108    private ClientModeManager.Listener mClientModeCallback = new ClientModeCallback();
109
110    WifiController(Context context, WifiStateMachine wsm, Looper wifiStateMachineLooper,
111                   WifiSettingsStore wss, Looper wifiServiceLooper, FrameworkFacade f,
112                   WifiStateMachinePrime wsmp) {
113        super(TAG, wifiServiceLooper);
114        mFacade = f;
115        mContext = context;
116        mWifiStateMachine = wsm;
117        mWifiStateMachineLooper = wifiStateMachineLooper;
118        mWifiStateMachinePrime = wsmp;
119        mSettingsStore = wss;
120
121        // CHECKSTYLE:OFF IndentationCheck
122        addState(mDefaultState);
123            addState(mStaDisabledState, mDefaultState);
124            addState(mStaEnabledState, mDefaultState);
125                addState(mDeviceActiveState, mStaEnabledState);
126            addState(mStaDisabledWithScanState, mDefaultState);
127            addState(mEcmState, mDefaultState);
128        // CHECKSTYLE:ON IndentationCheck
129
130        boolean isAirplaneModeOn = mSettingsStore.isAirplaneModeOn();
131        boolean isWifiEnabled = mSettingsStore.isWifiToggleEnabled();
132        boolean isScanningAlwaysAvailable = mSettingsStore.isScanAlwaysAvailable();
133        boolean isLocationModeActive =
134                mSettingsStore.getLocationModeSetting(mContext)
135                        == Settings.Secure.LOCATION_MODE_OFF;
136
137        log("isAirplaneModeOn = " + isAirplaneModeOn
138                + ", isWifiEnabled = " + isWifiEnabled
139                + ", isScanningAvailable = " + isScanningAlwaysAvailable
140                + ", isLocationModeActive = " + isLocationModeActive);
141
142        if (checkScanOnlyModeAvailable()) {
143            setInitialState(mStaDisabledWithScanState);
144        } else {
145            setInitialState(mStaDisabledState);
146        }
147
148        setLogRecSize(100);
149        setLogOnlyTransitions(false);
150
151        // register for state updates via callbacks (vs the intents registered below)
152        mWifiStateMachinePrime.registerScanOnlyCallback(mScanOnlyModeCallback);
153        mWifiStateMachinePrime.registerClientModeCallback(mClientModeCallback);
154
155        IntentFilter filter = new IntentFilter();
156        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
157        filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
158        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
159        filter.addAction(LocationManager.MODE_CHANGED_ACTION);
160        mContext.registerReceiver(
161                new BroadcastReceiver() {
162                    @Override
163                    public void onReceive(Context context, Intent intent) {
164                        String action = intent.getAction();
165                        if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
166                            mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
167                                    WifiManager.EXTRA_NETWORK_INFO);
168                        } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
169                            int state = intent.getIntExtra(
170                                    WifiManager.EXTRA_WIFI_AP_STATE,
171                                    WifiManager.WIFI_AP_STATE_FAILED);
172                            if (state == WifiManager.WIFI_AP_STATE_FAILED) {
173                                Log.e(TAG, "SoftAP start failed");
174                                sendMessage(CMD_AP_START_FAILURE);
175                            } else if (state == WifiManager.WIFI_AP_STATE_DISABLED) {
176                                sendMessage(CMD_AP_STOPPED);
177                            }
178                        } else if (action.equals(LocationManager.MODE_CHANGED_ACTION)) {
179                            // Location mode has been toggled...  trigger with the scan change
180                            // update to make sure we are in the correct mode
181                            sendMessage(CMD_SCAN_ALWAYS_MODE_CHANGED);
182                        }
183                    }
184                },
185                new IntentFilter(filter));
186
187        readWifiReEnableDelay();
188    }
189
190    private boolean checkScanOnlyModeAvailable() {
191        // first check if Location service is disabled, if so return false
192        if (mSettingsStore.getLocationModeSetting(mContext)
193                == Settings.Secure.LOCATION_MODE_OFF) {
194            return false;
195        }
196        return mSettingsStore.isScanAlwaysAvailable();
197    }
198
199    /**
200     * Listener used to receive scan mode updates - really needed for disabled updates to trigger
201     * mode changes.
202     */
203    private class ScanOnlyCallback implements ScanOnlyModeManager.Listener {
204        @Override
205        public void onStateChanged(int state) {
206            if (state == WifiManager.WIFI_STATE_UNKNOWN) {
207                Log.d(TAG, "ScanOnlyMode unexpected failure: state unknown");
208            } else if (state == WifiManager.WIFI_STATE_DISABLED) {
209                Log.d(TAG, "ScanOnlyMode stopped");
210                sendMessage(CMD_SCANNING_STOPPED);
211            } else if (state == WifiManager.WIFI_STATE_ENABLED) {
212                // scan mode is ready to go
213                Log.d(TAG, "scan mode active");
214            } else {
215                Log.d(TAG, "unexpected state update: " + state);
216            }
217        }
218    }
219
220    /**
221     * Listener used to receive client mode updates
222     */
223    private class ClientModeCallback implements ClientModeManager.Listener {
224        @Override
225        public void onStateChanged(int state) {
226            if (state == WifiManager.WIFI_STATE_UNKNOWN) {
227                logd("ClientMode unexpected failure: state unknown");
228                sendMessage(CMD_STA_START_FAILURE);
229            } else if (state == WifiManager.WIFI_STATE_DISABLED) {
230                logd("ClientMode stopped");
231                sendMessage(CMD_STA_STOPPED);
232            } else if (state == WifiManager.WIFI_STATE_ENABLED) {
233                // scan mode is ready to go
234                logd("client mode active");
235            } else {
236                logd("unexpected state update: " + state);
237            }
238        }
239    }
240
241    private void readWifiReEnableDelay() {
242        mReEnableDelayMillis = mFacade.getLongSetting(mContext,
243                Settings.Global.WIFI_REENABLE_DELAY_MS, DEFAULT_REENABLE_DELAY_MS);
244    }
245
246    private void updateBatteryWorkSource() {
247        mTmpWorkSource.clear();
248        mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource);
249    }
250
251    class DefaultState extends State {
252        @Override
253        public boolean processMessage(Message msg) {
254            switch (msg.what) {
255                case CMD_SCAN_ALWAYS_MODE_CHANGED:
256                case CMD_WIFI_TOGGLED:
257                case CMD_AP_START_FAILURE:
258                case CMD_SCANNING_STOPPED:
259                case CMD_STA_STOPPED:
260                case CMD_STA_START_FAILURE:
261                case CMD_RECOVERY_RESTART_WIFI_CONTINUE:
262                    break;
263                case CMD_RECOVERY_DISABLE_WIFI:
264                    log("Recovery has been throttled, disable wifi");
265                    mWifiStateMachinePrime.shutdownWifi();
266                    transitionTo(mStaDisabledState);
267                    break;
268                case CMD_RECOVERY_RESTART_WIFI:
269                    deferMessage(obtainMessage(CMD_RECOVERY_RESTART_WIFI_CONTINUE));
270                    mWifiStateMachinePrime.shutdownWifi();
271                    transitionTo(mStaDisabledState);
272                    break;
273                case CMD_USER_PRESENT:
274                    mFirstUserSignOnSeen = true;
275                    break;
276                case CMD_DEFERRED_TOGGLE:
277                    log("DEFERRED_TOGGLE ignored due to state change");
278                    break;
279                case CMD_SET_AP:
280                    // note: CMD_SET_AP is handled/dropped in ECM mode - will not start here
281
282                    // first make sure we aren't in airplane mode
283                    if (mSettingsStore.isAirplaneModeOn()) {
284                        log("drop softap requests when in airplane mode");
285                        break;
286                    }
287                    if (msg.arg1 == 1) {
288                        SoftApModeConfiguration config = (SoftApModeConfiguration) msg.obj;
289                        mWifiStateMachinePrime.enterSoftAPMode((SoftApModeConfiguration) msg.obj);
290                    } else {
291                        mWifiStateMachinePrime.stopSoftAPMode();
292                    }
293                    break;
294                case CMD_AIRPLANE_TOGGLED:
295                    if (mSettingsStore.isAirplaneModeOn()) {
296                        log("Airplane mode toggled, shutdown all modes");
297                        mWifiStateMachinePrime.shutdownWifi();
298                        transitionTo(mStaDisabledState);
299                    } else {
300                        log("Airplane mode disabled, determine next state");
301                        if (mSettingsStore.isWifiToggleEnabled()) {
302                            transitionTo(mDeviceActiveState);
303                        } else if (checkScanOnlyModeAvailable()) {
304                            transitionTo(mStaDisabledWithScanState);
305                        }
306                        // wifi should remain disabled, do not need to transition
307                    }
308                    break;
309                case CMD_EMERGENCY_CALL_STATE_CHANGED:
310                case CMD_EMERGENCY_MODE_CHANGED:
311                    boolean configWiFiDisableInECBM =
312                            mFacade.getConfigWiFiDisableInECBM(mContext);
313                    log("WifiController msg " + msg + " getConfigWiFiDisableInECBM "
314                            + configWiFiDisableInECBM);
315                    if ((msg.arg1 == 1) && configWiFiDisableInECBM) {
316                        transitionTo(mEcmState);
317                    }
318                    break;
319                case CMD_AP_STOPPED:
320                    log("SoftAp mode disabled, determine next state");
321                    if (mSettingsStore.isWifiToggleEnabled()) {
322                        transitionTo(mDeviceActiveState);
323                    } else if (checkScanOnlyModeAvailable()) {
324                        transitionTo(mStaDisabledWithScanState);
325                    }
326                    // wifi should remain disabled, do not need to transition
327                    break;
328                default:
329                    throw new RuntimeException("WifiController.handleMessage " + msg.what);
330            }
331            return HANDLED;
332        }
333
334    }
335
336    class StaDisabledState extends State {
337        private int mDeferredEnableSerialNumber = 0;
338        private boolean mHaveDeferredEnable = false;
339        private long mDisabledTimestamp;
340
341        @Override
342        public void enter() {
343            mWifiStateMachinePrime.disableWifi();
344            // Supplicant can't restart right away, so note the time we switched off
345            mDisabledTimestamp = SystemClock.elapsedRealtime();
346            mDeferredEnableSerialNumber++;
347            mHaveDeferredEnable = false;
348            mWifiStateMachine.clearANQPCache();
349        }
350        @Override
351        public boolean processMessage(Message msg) {
352            switch (msg.what) {
353                case CMD_WIFI_TOGGLED:
354                    if (mSettingsStore.isWifiToggleEnabled()) {
355                        if (doDeferEnable(msg)) {
356                            if (mHaveDeferredEnable) {
357                                //  have 2 toggles now, inc serial number and ignore both
358                                mDeferredEnableSerialNumber++;
359                            }
360                            mHaveDeferredEnable = !mHaveDeferredEnable;
361                            break;
362                        }
363                        transitionTo(mDeviceActiveState);
364                    } else if (checkScanOnlyModeAvailable()) {
365                        // only go to scan mode if we aren't in airplane mode
366                        if (mSettingsStore.isAirplaneModeOn()) {
367                            transitionTo(mStaDisabledWithScanState);
368                        }
369                    }
370                    break;
371                case CMD_SCAN_ALWAYS_MODE_CHANGED:
372                    if (checkScanOnlyModeAvailable()) {
373                        transitionTo(mStaDisabledWithScanState);
374                        break;
375                    }
376                    break;
377                case CMD_SET_AP:
378                    // first make sure we aren't in airplane mode
379                    if (mSettingsStore.isAirplaneModeOn()) {
380                        log("drop softap requests when in airplane mode");
381                        break;
382                    }
383                    if (msg.arg1 == 1) {
384                        // remember that we were disabled, but pass the command up to start softap
385                        mSettingsStore.setWifiSavedState(WifiSettingsStore.WIFI_DISABLED);
386                    }
387                    return NOT_HANDLED;
388                case CMD_DEFERRED_TOGGLE:
389                    if (msg.arg1 != mDeferredEnableSerialNumber) {
390                        log("DEFERRED_TOGGLE ignored due to serial mismatch");
391                        break;
392                    }
393                    log("DEFERRED_TOGGLE handled");
394                    sendMessage((Message)(msg.obj));
395                    break;
396                case CMD_RECOVERY_RESTART_WIFI_CONTINUE:
397                    if (mSettingsStore.isWifiToggleEnabled()) {
398                        // wifi is currently disabled but the toggle is on, must have had an
399                        // interface down before the recovery triggered
400                        transitionTo(mDeviceActiveState);
401                        break;
402                    } else if (checkScanOnlyModeAvailable()) {
403                        transitionTo(mStaDisabledWithScanState);
404                        break;
405                    }
406                    break;
407                default:
408                    return NOT_HANDLED;
409            }
410            return HANDLED;
411        }
412
413        private boolean doDeferEnable(Message msg) {
414            long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp;
415            if (delaySoFar >= mReEnableDelayMillis) {
416                return false;
417            }
418
419            log("WifiController msg " + msg + " deferred for " +
420                    (mReEnableDelayMillis - delaySoFar) + "ms");
421
422            // need to defer this action.
423            Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE);
424            deferredMsg.obj = Message.obtain(msg);
425            deferredMsg.arg1 = ++mDeferredEnableSerialNumber;
426            sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS);
427            return true;
428        }
429
430    }
431
432    class StaEnabledState extends State {
433        @Override
434        public void enter() {
435            log("StaEnabledState.enter()");
436        }
437
438        @Override
439        public boolean processMessage(Message msg) {
440            switch (msg.what) {
441                case CMD_WIFI_TOGGLED:
442                    if (! mSettingsStore.isWifiToggleEnabled()) {
443                        if (checkScanOnlyModeAvailable()) {
444                            transitionTo(mStaDisabledWithScanState);
445                        } else {
446                            transitionTo(mStaDisabledState);
447                        }
448                    }
449                    break;
450                case CMD_AIRPLANE_TOGGLED:
451                    // airplane mode toggled on is handled in the default state
452                    if (mSettingsStore.isAirplaneModeOn()) {
453                        return NOT_HANDLED;
454                    } else {
455                        // when airplane mode is toggled off, but wifi is on, we can keep it on
456                        log("airplane mode toggled - and airplane mode is off.  return handled");
457                        return HANDLED;
458                    }
459                case CMD_STA_START_FAILURE:
460                    if (!checkScanOnlyModeAvailable()) {
461                        transitionTo(mStaDisabledState);
462                    } else {
463                        transitionTo(mStaDisabledWithScanState);
464                    }
465                    break;
466                case CMD_SET_AP:
467                    if (msg.arg1 == 1) {
468                        // remember that we were enabled, but pass the command up to start softap
469                        mSettingsStore.setWifiSavedState(WifiSettingsStore.WIFI_ENABLED);
470                    }
471                    return NOT_HANDLED;
472                case CMD_AP_START_FAILURE:
473                case CMD_AP_STOPPED:
474                    // already in a wifi mode, no need to check where we should go with softap
475                    // stopped
476                    break;
477                case CMD_STA_STOPPED:
478                    // Client mode stopped.  head to Disabled to wait for next command
479                    transitionTo(mStaDisabledState);
480                    break;
481                default:
482                    return NOT_HANDLED;
483
484            }
485            return HANDLED;
486        }
487    }
488
489    class StaDisabledWithScanState extends State {
490        private int mDeferredEnableSerialNumber = 0;
491        private boolean mHaveDeferredEnable = false;
492        private long mDisabledTimestamp;
493
494        @Override
495        public void enter() {
496            // now trigger the actual mode switch in WifiStateMachinePrime
497            mWifiStateMachinePrime.enterScanOnlyMode();
498
499            // TODO b/71559473: remove the defered enable after mode management changes are complete
500            // Supplicant can't restart right away, so not the time we switched off
501            mDisabledTimestamp = SystemClock.elapsedRealtime();
502            mDeferredEnableSerialNumber++;
503            mHaveDeferredEnable = false;
504        }
505
506        @Override
507        public boolean processMessage(Message msg) {
508            switch (msg.what) {
509                case CMD_WIFI_TOGGLED:
510                    if (mSettingsStore.isWifiToggleEnabled()) {
511                        if (doDeferEnable(msg)) {
512                            if (mHaveDeferredEnable) {
513                                // have 2 toggles now, inc serial number and ignore both
514                                mDeferredEnableSerialNumber++;
515                            }
516                            mHaveDeferredEnable = !mHaveDeferredEnable;
517                            break;
518                        }
519                        transitionTo(mDeviceActiveState);
520                    }
521                    break;
522                case CMD_SCAN_ALWAYS_MODE_CHANGED:
523                    if (!checkScanOnlyModeAvailable()) {
524                        log("StaDisabledWithScanState: scan no longer available");
525                        transitionTo(mStaDisabledState);
526                    }
527                    break;
528                case CMD_SET_AP:
529                    if (msg.arg1 == 1) {
530                        // remember that we were disabled, but pass the command up to start softap
531                        mSettingsStore.setWifiSavedState(WifiSettingsStore.WIFI_DISABLED);
532                    }
533                    return NOT_HANDLED;
534                case CMD_DEFERRED_TOGGLE:
535                    if (msg.arg1 != mDeferredEnableSerialNumber) {
536                        log("DEFERRED_TOGGLE ignored due to serial mismatch");
537                        break;
538                    }
539                    logd("DEFERRED_TOGGLE handled");
540                    sendMessage((Message)(msg.obj));
541                    break;
542                case CMD_AP_START_FAILURE:
543                case CMD_AP_STOPPED:
544                    // already in a wifi mode, no need to check where we should go with softap
545                    // stopped
546                    break;
547                case CMD_SCANNING_STOPPED:
548                    // stopped due to interface destruction - return to disabled and wait
549                    log("WifiController: SCANNING_STOPPED when in scan mode -> StaDisabled");
550                    transitionTo(mStaDisabledState);
551                    break;
552                default:
553                    return NOT_HANDLED;
554            }
555            return HANDLED;
556        }
557
558        private boolean doDeferEnable(Message msg) {
559            long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp;
560            if (delaySoFar >= mReEnableDelayMillis) {
561                return false;
562            }
563
564            log("WifiController msg " + msg + " deferred for " +
565                    (mReEnableDelayMillis - delaySoFar) + "ms");
566
567            // need to defer this action.
568            Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE);
569            deferredMsg.obj = Message.obtain(msg);
570            deferredMsg.arg1 = ++mDeferredEnableSerialNumber;
571            sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS);
572            return true;
573        }
574
575    }
576
577    /**
578     * Determine the next state based on the current settings (e.g. saved
579     * wifi state).
580     */
581    private State getNextWifiState() {
582        if (mSettingsStore.getWifiSavedState() == WifiSettingsStore.WIFI_ENABLED) {
583            return mDeviceActiveState;
584        }
585
586        if (checkScanOnlyModeAvailable()) {
587            return mStaDisabledWithScanState;
588        }
589
590        return mStaDisabledState;
591    }
592
593    class EcmState extends State {
594        // we can enter EcmState either because an emergency call started or because
595        // emergency callback mode started. This count keeps track of how many such
596        // events happened; so we can exit after all are undone
597
598        private int mEcmEntryCount;
599        @Override
600        public void enter() {
601            mWifiStateMachinePrime.shutdownWifi();
602            mWifiStateMachine.clearANQPCache();
603            mEcmEntryCount = 1;
604        }
605
606        /**
607         * Hanles messages received while in EcmMode.
608         *
609         * TODO (b/78244565): move from many ifs to a switch
610         */
611        @Override
612        public boolean processMessage(Message msg) {
613            if (msg.what == CMD_EMERGENCY_CALL_STATE_CHANGED) {
614                if (msg.arg1 == 1) {
615                    // nothing to do - just says emergency call started
616                    mEcmEntryCount++;
617                } else if (msg.arg1 == 0) {
618                    // emergency call ended
619                    decrementCountAndReturnToAppropriateState();
620                }
621                return HANDLED;
622            } else if (msg.what == CMD_EMERGENCY_MODE_CHANGED) {
623
624                if (msg.arg1 == 1) {
625                    // Transitioned into emergency callback mode
626                    mEcmEntryCount++;
627                } else if (msg.arg1 == 0) {
628                    // out of emergency callback mode
629                    decrementCountAndReturnToAppropriateState();
630                }
631                return HANDLED;
632            } else if (msg.what == CMD_RECOVERY_RESTART_WIFI
633                    || msg.what == CMD_RECOVERY_DISABLE_WIFI) {
634                // do not want to restart wifi if we are in emergency mode
635                return HANDLED;
636            } else if (msg.what == CMD_AP_STOPPED || msg.what == CMD_SCANNING_STOPPED
637                    || msg.what == CMD_STA_STOPPED) {
638                // do not want to trigger a mode switch if we are in emergency mode
639                return HANDLED;
640            } else if (msg.what == CMD_SET_AP) {
641                // do not want to start softap if we are in emergency mode
642                return HANDLED;
643            } else {
644                return NOT_HANDLED;
645            }
646        }
647
648        private void decrementCountAndReturnToAppropriateState() {
649            boolean exitEcm = false;
650
651            if (mEcmEntryCount == 0) {
652                loge("mEcmEntryCount is 0; exiting Ecm");
653                exitEcm = true;
654            } else if (--mEcmEntryCount == 0) {
655                exitEcm = true;
656            }
657
658            if (exitEcm) {
659                if (mSettingsStore.isWifiToggleEnabled()) {
660                    transitionTo(mDeviceActiveState);
661                } else if (checkScanOnlyModeAvailable()) {
662                    transitionTo(mStaDisabledWithScanState);
663                } else {
664                    transitionTo(mStaDisabledState);
665                }
666            }
667        }
668    }
669
670    /**
671     * Parent: StaEnabledState
672     *
673     * TODO (b/79209870): merge DeviceActiveState and StaEnabledState into a single state
674     */
675    class DeviceActiveState extends State {
676        @Override
677        public void enter() {
678            mWifiStateMachinePrime.enterClientMode();
679            mWifiStateMachine.setHighPerfModeEnabled(false);
680        }
681
682        @Override
683        public boolean processMessage(Message msg) {
684            if (msg.what == CMD_USER_PRESENT) {
685                // TLS networks can't connect until user unlocks keystore. KeyStore
686                // unlocks when the user punches PIN after the reboot. So use this
687                // trigger to get those networks connected.
688                if (mFirstUserSignOnSeen == false) {
689                    mWifiStateMachine.reloadTlsNetworksAndReconnect();
690                }
691                mFirstUserSignOnSeen = true;
692                return HANDLED;
693            } else if (msg.what == CMD_RECOVERY_RESTART_WIFI) {
694                final String bugTitle;
695                final String bugDetail;
696                if (msg.arg1 < SelfRecovery.REASON_STRINGS.length && msg.arg1 >= 0) {
697                    bugDetail = SelfRecovery.REASON_STRINGS[msg.arg1];
698                    bugTitle = "Wi-Fi BugReport: " + bugDetail;
699                } else {
700                    bugDetail = "";
701                    bugTitle = "Wi-Fi BugReport";
702                }
703                if (msg.arg1 != SelfRecovery.REASON_LAST_RESORT_WATCHDOG) {
704                    (new Handler(mWifiStateMachineLooper)).post(() -> {
705                        mWifiStateMachine.takeBugReport(bugTitle, bugDetail);
706                    });
707                }
708                return NOT_HANDLED;
709            }
710            return NOT_HANDLED;
711        }
712    }
713}
714