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 
17 package com.android.server.wifi;
18 
19 import static com.android.server.wifi.util.ApConfigUtil.ERROR_GENERIC;
20 import static com.android.server.wifi.util.ApConfigUtil.ERROR_NO_CHANNEL;
21 import static com.android.server.wifi.util.ApConfigUtil.SUCCESS;
22 
23 import android.annotation.NonNull;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.database.ContentObserver;
27 import android.net.wifi.ScanResult;
28 import android.net.wifi.WifiConfiguration;
29 import android.net.wifi.WifiManager;
30 import android.os.Handler;
31 import android.os.Looper;
32 import android.os.Message;
33 import android.os.SystemClock;
34 import android.os.UserHandle;
35 import android.provider.Settings;
36 import android.text.TextUtils;
37 import android.util.Log;
38 
39 import com.android.internal.R;
40 import com.android.internal.annotations.VisibleForTesting;
41 import com.android.internal.util.IState;
42 import com.android.internal.util.State;
43 import com.android.internal.util.StateMachine;
44 import com.android.internal.util.WakeupMessage;
45 import com.android.server.wifi.WifiNative.InterfaceCallback;
46 import com.android.server.wifi.WifiNative.SoftApListener;
47 import com.android.server.wifi.util.ApConfigUtil;
48 
49 import java.io.FileDescriptor;
50 import java.io.PrintWriter;
51 import java.util.Locale;
52 
53 /**
54  * Manage WiFi in AP mode.
55  * The internal state machine runs under the ClientModeImpl handler thread context.
56  */
57 public class SoftApManager implements ActiveModeManager {
58     private static final String TAG = "SoftApManager";
59 
60     // Minimum limit to use for timeout delay if the value from overlay setting is too small.
61     private static final int MIN_SOFT_AP_TIMEOUT_DELAY_MS = 600_000;  // 10 minutes
62 
63     @VisibleForTesting
64     public static final String SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG = TAG
65             + " Soft AP Send Message Timeout";
66 
67     private final Context mContext;
68     private final FrameworkFacade mFrameworkFacade;
69     private final WifiNative mWifiNative;
70 
71     private final String mCountryCode;
72 
73     private final SoftApStateMachine mStateMachine;
74 
75     private final WifiManager.SoftApCallback mCallback;
76 
77     private String mApInterfaceName;
78     private boolean mIfaceIsUp;
79     private boolean mIfaceIsDestroyed;
80 
81     private final WifiApConfigStore mWifiApConfigStore;
82 
83     private final WifiMetrics mWifiMetrics;
84 
85     private final int mMode;
86     private WifiConfiguration mApConfig;
87 
88     private int mReportedFrequency = -1;
89     private int mReportedBandwidth = -1;
90 
91     private int mNumAssociatedStations = 0;
92     private boolean mTimeoutEnabled = false;
93 
94     private final SarManager mSarManager;
95 
96     private long mStartTimestamp = -1;
97 
98     /**
99      * Listener for soft AP events.
100      */
101     private final SoftApListener mSoftApListener = new SoftApListener() {
102 
103         @Override
104         public void onFailure() {
105             mStateMachine.sendMessage(SoftApStateMachine.CMD_FAILURE);
106         }
107 
108         @Override
109         public void onNumAssociatedStationsChanged(int numStations) {
110             mStateMachine.sendMessage(
111                     SoftApStateMachine.CMD_NUM_ASSOCIATED_STATIONS_CHANGED, numStations);
112         }
113 
114         @Override
115         public void onSoftApChannelSwitched(int frequency, int bandwidth) {
116             mStateMachine.sendMessage(
117                     SoftApStateMachine.CMD_SOFT_AP_CHANNEL_SWITCHED, frequency, bandwidth);
118         }
119     };
120 
SoftApManager(@onNull Context context, @NonNull Looper looper, @NonNull FrameworkFacade framework, @NonNull WifiNative wifiNative, String countryCode, @NonNull WifiManager.SoftApCallback callback, @NonNull WifiApConfigStore wifiApConfigStore, @NonNull SoftApModeConfiguration apConfig, @NonNull WifiMetrics wifiMetrics, @NonNull SarManager sarManager)121     public SoftApManager(@NonNull Context context,
122                          @NonNull Looper looper,
123                          @NonNull FrameworkFacade framework,
124                          @NonNull WifiNative wifiNative,
125                          String countryCode,
126                          @NonNull WifiManager.SoftApCallback callback,
127                          @NonNull WifiApConfigStore wifiApConfigStore,
128                          @NonNull SoftApModeConfiguration apConfig,
129                          @NonNull WifiMetrics wifiMetrics,
130                          @NonNull SarManager sarManager) {
131         mContext = context;
132         mFrameworkFacade = framework;
133         mWifiNative = wifiNative;
134         mCountryCode = countryCode;
135         mCallback = callback;
136         mWifiApConfigStore = wifiApConfigStore;
137         mMode = apConfig.getTargetMode();
138         WifiConfiguration config = apConfig.getWifiConfiguration();
139         if (config == null) {
140             mApConfig = mWifiApConfigStore.getApConfiguration();
141         } else {
142             mApConfig = config;
143         }
144         mWifiMetrics = wifiMetrics;
145         mSarManager = sarManager;
146         mStateMachine = new SoftApStateMachine(looper);
147     }
148 
149     /**
150      * Start soft AP with the supplied config.
151      */
start()152     public void start() {
153         mStateMachine.sendMessage(SoftApStateMachine.CMD_START, mApConfig);
154     }
155 
156     /**
157      * Stop soft AP.
158      */
stop()159     public void stop() {
160         Log.d(TAG, " currentstate: " + getCurrentStateName());
161         if (mApInterfaceName != null) {
162             if (mIfaceIsUp) {
163                 updateApState(WifiManager.WIFI_AP_STATE_DISABLING,
164                         WifiManager.WIFI_AP_STATE_ENABLED, 0);
165             } else {
166                 updateApState(WifiManager.WIFI_AP_STATE_DISABLING,
167                         WifiManager.WIFI_AP_STATE_ENABLING, 0);
168             }
169         }
170         mStateMachine.quitNow();
171     }
172 
getScanMode()173     public @ScanMode int getScanMode() {
174         return SCAN_NONE;
175     }
176 
getIpMode()177     public int getIpMode() {
178         return mMode;
179     }
180 
181     /**
182      * Dump info about this softap manager.
183      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)184     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
185         pw.println("--Dump of SoftApManager--");
186 
187         pw.println("current StateMachine mode: " + getCurrentStateName());
188         pw.println("mApInterfaceName: " + mApInterfaceName);
189         pw.println("mIfaceIsUp: " + mIfaceIsUp);
190         pw.println("mMode: " + mMode);
191         pw.println("mCountryCode: " + mCountryCode);
192         if (mApConfig != null) {
193             pw.println("mApConfig.SSID: " + mApConfig.SSID);
194             pw.println("mApConfig.apBand: " + mApConfig.apBand);
195             pw.println("mApConfig.hiddenSSID: " + mApConfig.hiddenSSID);
196         } else {
197             pw.println("mApConfig: null");
198         }
199         pw.println("mNumAssociatedStations: " + mNumAssociatedStations);
200         pw.println("mTimeoutEnabled: " + mTimeoutEnabled);
201         pw.println("mReportedFrequency: " + mReportedFrequency);
202         pw.println("mReportedBandwidth: " + mReportedBandwidth);
203         pw.println("mStartTimestamp: " + mStartTimestamp);
204     }
205 
getCurrentStateName()206     private String getCurrentStateName() {
207         IState currentState = mStateMachine.getCurrentState();
208 
209         if (currentState != null) {
210             return currentState.getName();
211         }
212 
213         return "StateMachine not active";
214     }
215 
216     /**
217      * Update AP state.
218      * @param newState new AP state
219      * @param currentState current AP state
220      * @param reason Failure reason if the new AP state is in failure state
221      */
updateApState(int newState, int currentState, int reason)222     private void updateApState(int newState, int currentState, int reason) {
223         mCallback.onStateChanged(newState, reason);
224 
225         //send the AP state change broadcast
226         final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
227         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
228         intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, newState);
229         intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, currentState);
230         if (newState == WifiManager.WIFI_AP_STATE_FAILED) {
231             //only set reason number when softAP start failed
232             intent.putExtra(WifiManager.EXTRA_WIFI_AP_FAILURE_REASON, reason);
233         }
234 
235         intent.putExtra(WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME, mApInterfaceName);
236         intent.putExtra(WifiManager.EXTRA_WIFI_AP_MODE, mMode);
237         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
238     }
239 
240     /**
241      * Start a soft AP instance with the given configuration.
242      * @param config AP configuration
243      * @return integer result code
244      */
startSoftAp(WifiConfiguration config)245     private int startSoftAp(WifiConfiguration config) {
246         if (config == null || config.SSID == null) {
247             Log.e(TAG, "Unable to start soft AP without valid configuration");
248             return ERROR_GENERIC;
249         }
250         // Setup country code
251         if (TextUtils.isEmpty(mCountryCode)) {
252             if (config.apBand == WifiConfiguration.AP_BAND_5GHZ) {
253                 // Country code is mandatory for 5GHz band.
254                 Log.e(TAG, "Invalid country code, required for setting up "
255                         + "soft ap in 5GHz");
256                 return ERROR_GENERIC;
257             }
258             // Absence of country code is not fatal for 2Ghz & Any band options.
259         } else if (!mWifiNative.setCountryCodeHal(
260                 mApInterfaceName, mCountryCode.toUpperCase(Locale.ROOT))) {
261             if (config.apBand == WifiConfiguration.AP_BAND_5GHZ) {
262                 // Return an error if failed to set country code when AP is configured for
263                 // 5GHz band.
264                 Log.e(TAG, "Failed to set country code, required for setting up "
265                         + "soft ap in 5GHz");
266                 return ERROR_GENERIC;
267             }
268             // Failure to set country code is not fatal for 2Ghz & Any band options.
269         }
270 
271         // Make a copy of configuration for updating AP band and channel.
272         WifiConfiguration localConfig = new WifiConfiguration(config);
273 
274         int result = ApConfigUtil.updateApChannelConfig(
275                 mWifiNative, mCountryCode,
276                 mWifiApConfigStore.getAllowed2GChannel(), localConfig);
277 
278         if (result != SUCCESS) {
279             Log.e(TAG, "Failed to update AP band and channel");
280             return result;
281         }
282 
283         if (localConfig.hiddenSSID) {
284             Log.d(TAG, "SoftAP is a hidden network");
285         }
286         if (!mWifiNative.startSoftAp(mApInterfaceName, localConfig, mSoftApListener)) {
287             Log.e(TAG, "Soft AP start failed");
288             return ERROR_GENERIC;
289         }
290         mStartTimestamp = SystemClock.elapsedRealtime();
291         Log.d(TAG, "Soft AP is started");
292 
293         return SUCCESS;
294     }
295 
296     /**
297      * Teardown soft AP and teardown the interface.
298      */
stopSoftAp()299     private void stopSoftAp() {
300         mWifiNative.teardownInterface(mApInterfaceName);
301         Log.d(TAG, "Soft AP is stopped");
302     }
303 
304     private class SoftApStateMachine extends StateMachine {
305         // Commands for the state machine.
306         public static final int CMD_START = 0;
307         public static final int CMD_FAILURE = 2;
308         public static final int CMD_INTERFACE_STATUS_CHANGED = 3;
309         public static final int CMD_NUM_ASSOCIATED_STATIONS_CHANGED = 4;
310         public static final int CMD_NO_ASSOCIATED_STATIONS_TIMEOUT = 5;
311         public static final int CMD_TIMEOUT_TOGGLE_CHANGED = 6;
312         public static final int CMD_INTERFACE_DESTROYED = 7;
313         public static final int CMD_INTERFACE_DOWN = 8;
314         public static final int CMD_SOFT_AP_CHANNEL_SWITCHED = 9;
315 
316         private final State mIdleState = new IdleState();
317         private final State mStartedState = new StartedState();
318 
319         private final InterfaceCallback mWifiNativeInterfaceCallback = new InterfaceCallback() {
320             @Override
321             public void onDestroyed(String ifaceName) {
322                 if (mApInterfaceName != null && mApInterfaceName.equals(ifaceName)) {
323                     sendMessage(CMD_INTERFACE_DESTROYED);
324                 }
325             }
326 
327             @Override
328             public void onUp(String ifaceName) {
329                 if (mApInterfaceName != null && mApInterfaceName.equals(ifaceName)) {
330                     sendMessage(CMD_INTERFACE_STATUS_CHANGED, 1);
331                 }
332             }
333 
334             @Override
335             public void onDown(String ifaceName) {
336                 if (mApInterfaceName != null && mApInterfaceName.equals(ifaceName)) {
337                     sendMessage(CMD_INTERFACE_STATUS_CHANGED, 0);
338                 }
339             }
340         };
341 
SoftApStateMachine(Looper looper)342         SoftApStateMachine(Looper looper) {
343             super(TAG, looper);
344 
345             addState(mIdleState);
346             addState(mStartedState);
347 
348             setInitialState(mIdleState);
349             start();
350         }
351 
352         private class IdleState extends State {
353             @Override
enter()354             public void enter() {
355                 mApInterfaceName = null;
356                 mIfaceIsUp = false;
357                 mIfaceIsDestroyed = false;
358             }
359 
360             @Override
processMessage(Message message)361             public boolean processMessage(Message message) {
362                 switch (message.what) {
363                     case CMD_START:
364                         mApInterfaceName = mWifiNative.setupInterfaceForSoftApMode(
365                                 mWifiNativeInterfaceCallback);
366                         if (TextUtils.isEmpty(mApInterfaceName)) {
367                             Log.e(TAG, "setup failure when creating ap interface.");
368                             updateApState(WifiManager.WIFI_AP_STATE_FAILED,
369                                     WifiManager.WIFI_AP_STATE_DISABLED,
370                                     WifiManager.SAP_START_FAILURE_GENERAL);
371                             mWifiMetrics.incrementSoftApStartResult(
372                                     false, WifiManager.SAP_START_FAILURE_GENERAL);
373                             break;
374                         }
375                         updateApState(WifiManager.WIFI_AP_STATE_ENABLING,
376                                 WifiManager.WIFI_AP_STATE_DISABLED, 0);
377                         int result = startSoftAp((WifiConfiguration) message.obj);
378                         if (result != SUCCESS) {
379                             int failureReason = WifiManager.SAP_START_FAILURE_GENERAL;
380                             if (result == ERROR_NO_CHANNEL) {
381                                 failureReason = WifiManager.SAP_START_FAILURE_NO_CHANNEL;
382                             }
383                             updateApState(WifiManager.WIFI_AP_STATE_FAILED,
384                                           WifiManager.WIFI_AP_STATE_ENABLING,
385                                           failureReason);
386                             stopSoftAp();
387                             mWifiMetrics.incrementSoftApStartResult(false, failureReason);
388                             break;
389                         }
390                         transitionTo(mStartedState);
391                         break;
392                     default:
393                         // Ignore all other commands.
394                         break;
395                 }
396 
397                 return HANDLED;
398             }
399         }
400 
401         private class StartedState extends State {
402             private int mTimeoutDelay;
403             private WakeupMessage mSoftApTimeoutMessage;
404             private SoftApTimeoutEnabledSettingObserver mSettingObserver;
405 
406             /**
407             * Observer for timeout settings changes.
408             */
409             private class SoftApTimeoutEnabledSettingObserver extends ContentObserver {
SoftApTimeoutEnabledSettingObserver(Handler handler)410                 SoftApTimeoutEnabledSettingObserver(Handler handler) {
411                     super(handler);
412                 }
413 
register()414                 public void register() {
415                     mFrameworkFacade.registerContentObserver(mContext,
416                             Settings.Global.getUriFor(Settings.Global.SOFT_AP_TIMEOUT_ENABLED),
417                             true, this);
418                     mTimeoutEnabled = getValue();
419                 }
420 
unregister()421                 public void unregister() {
422                     mFrameworkFacade.unregisterContentObserver(mContext, this);
423                 }
424 
425                 @Override
onChange(boolean selfChange)426                 public void onChange(boolean selfChange) {
427                     super.onChange(selfChange);
428                     mStateMachine.sendMessage(SoftApStateMachine.CMD_TIMEOUT_TOGGLE_CHANGED,
429                             getValue() ? 1 : 0);
430                 }
431 
getValue()432                 private boolean getValue() {
433                     boolean enabled = mFrameworkFacade.getIntegerSetting(mContext,
434                             Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1) == 1;
435                     return enabled;
436                 }
437             }
438 
getConfigSoftApTimeoutDelay()439             private int getConfigSoftApTimeoutDelay() {
440                 int delay = mContext.getResources().getInteger(
441                         R.integer.config_wifi_framework_soft_ap_timeout_delay);
442                 if (delay < MIN_SOFT_AP_TIMEOUT_DELAY_MS) {
443                     delay = MIN_SOFT_AP_TIMEOUT_DELAY_MS;
444                     Log.w(TAG, "Overriding timeout delay with minimum limit value");
445                 }
446                 Log.d(TAG, "Timeout delay: " + delay);
447                 return delay;
448             }
449 
scheduleTimeoutMessage()450             private void scheduleTimeoutMessage() {
451                 if (!mTimeoutEnabled) {
452                     return;
453                 }
454                 mSoftApTimeoutMessage.schedule(SystemClock.elapsedRealtime() + mTimeoutDelay);
455                 Log.d(TAG, "Timeout message scheduled");
456             }
457 
cancelTimeoutMessage()458             private void cancelTimeoutMessage() {
459                 mSoftApTimeoutMessage.cancel();
460                 Log.d(TAG, "Timeout message canceled");
461             }
462 
463             /**
464              * Set number of stations associated with this soft AP
465              * @param numStations Number of connected stations
466              */
setNumAssociatedStations(int numStations)467             private void setNumAssociatedStations(int numStations) {
468                 if (mNumAssociatedStations == numStations) {
469                     return;
470                 }
471                 mNumAssociatedStations = numStations;
472                 Log.d(TAG, "Number of associated stations changed: " + mNumAssociatedStations);
473 
474                 if (mCallback != null) {
475                     mCallback.onNumClientsChanged(mNumAssociatedStations);
476                 } else {
477                     Log.e(TAG, "SoftApCallback is null. Dropping NumClientsChanged event.");
478                 }
479                 mWifiMetrics.addSoftApNumAssociatedStationsChangedEvent(mNumAssociatedStations,
480                         mMode);
481 
482                 if (mNumAssociatedStations == 0) {
483                     scheduleTimeoutMessage();
484                 } else {
485                     cancelTimeoutMessage();
486                 }
487             }
488 
onUpChanged(boolean isUp)489             private void onUpChanged(boolean isUp) {
490                 if (isUp == mIfaceIsUp) {
491                     return;  // no change
492                 }
493                 mIfaceIsUp = isUp;
494                 if (isUp) {
495                     Log.d(TAG, "SoftAp is ready for use");
496                     updateApState(WifiManager.WIFI_AP_STATE_ENABLED,
497                             WifiManager.WIFI_AP_STATE_ENABLING, 0);
498                     mWifiMetrics.incrementSoftApStartResult(true, 0);
499                     if (mCallback != null) {
500                         mCallback.onNumClientsChanged(mNumAssociatedStations);
501                     }
502                 } else {
503                     // the interface was up, but goes down
504                     sendMessage(CMD_INTERFACE_DOWN);
505                 }
506                 mWifiMetrics.addSoftApUpChangedEvent(isUp, mMode);
507             }
508 
509             @Override
enter()510             public void enter() {
511                 mIfaceIsUp = false;
512                 mIfaceIsDestroyed = false;
513                 onUpChanged(mWifiNative.isInterfaceUp(mApInterfaceName));
514 
515                 mTimeoutDelay = getConfigSoftApTimeoutDelay();
516                 Handler handler = mStateMachine.getHandler();
517                 mSoftApTimeoutMessage = new WakeupMessage(mContext, handler,
518                         SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG,
519                         SoftApStateMachine.CMD_NO_ASSOCIATED_STATIONS_TIMEOUT);
520                 mSettingObserver = new SoftApTimeoutEnabledSettingObserver(handler);
521 
522                 if (mSettingObserver != null) {
523                     mSettingObserver.register();
524                 }
525 
526                 mSarManager.setSapWifiState(WifiManager.WIFI_AP_STATE_ENABLED);
527 
528                 Log.d(TAG, "Resetting num stations on start");
529                 mNumAssociatedStations = 0;
530                 scheduleTimeoutMessage();
531             }
532 
533             @Override
exit()534             public void exit() {
535                 if (!mIfaceIsDestroyed) {
536                     stopSoftAp();
537                 }
538 
539                 if (mSettingObserver != null) {
540                     mSettingObserver.unregister();
541                 }
542                 Log.d(TAG, "Resetting num stations on stop");
543                 setNumAssociatedStations(0);
544                 cancelTimeoutMessage();
545                 // Need this here since we are exiting |Started| state and won't handle any
546                 // future CMD_INTERFACE_STATUS_CHANGED events after this point
547                 mWifiMetrics.addSoftApUpChangedEvent(false, mMode);
548                 updateApState(WifiManager.WIFI_AP_STATE_DISABLED,
549                         WifiManager.WIFI_AP_STATE_DISABLING, 0);
550 
551                 mSarManager.setSapWifiState(WifiManager.WIFI_AP_STATE_DISABLED);
552                 mApInterfaceName = null;
553                 mIfaceIsUp = false;
554                 mIfaceIsDestroyed = false;
555                 mStateMachine.quitNow();
556             }
557 
updateUserBandPreferenceViolationMetricsIfNeeded()558             private void updateUserBandPreferenceViolationMetricsIfNeeded() {
559                 boolean bandPreferenceViolated = false;
560                 if (mApConfig.apBand == WifiConfiguration.AP_BAND_2GHZ
561                         && ScanResult.is5GHz(mReportedFrequency)) {
562                     bandPreferenceViolated = true;
563                 } else if (mApConfig.apBand == WifiConfiguration.AP_BAND_5GHZ
564                         && ScanResult.is24GHz(mReportedFrequency)) {
565                     bandPreferenceViolated = true;
566                 }
567                 if (bandPreferenceViolated) {
568                     Log.e(TAG, "Channel does not satisfy user band preference: "
569                             + mReportedFrequency);
570                     mWifiMetrics.incrementNumSoftApUserBandPreferenceUnsatisfied();
571                 }
572             }
573 
574             @Override
processMessage(Message message)575             public boolean processMessage(Message message) {
576                 switch (message.what) {
577                     case CMD_NUM_ASSOCIATED_STATIONS_CHANGED:
578                         if (message.arg1 < 0) {
579                             Log.e(TAG, "Invalid number of associated stations: " + message.arg1);
580                             break;
581                         }
582                         Log.d(TAG, "Setting num stations on CMD_NUM_ASSOCIATED_STATIONS_CHANGED");
583                         setNumAssociatedStations(message.arg1);
584                         break;
585                     case CMD_SOFT_AP_CHANNEL_SWITCHED:
586                         mReportedFrequency = message.arg1;
587                         mReportedBandwidth = message.arg2;
588                         Log.d(TAG, "Channel switched. Frequency: " + mReportedFrequency
589                                 + " Bandwidth: " + mReportedBandwidth);
590                         mWifiMetrics.addSoftApChannelSwitchedEvent(mReportedFrequency,
591                                 mReportedBandwidth, mMode);
592                         updateUserBandPreferenceViolationMetricsIfNeeded();
593                         break;
594                     case CMD_TIMEOUT_TOGGLE_CHANGED:
595                         boolean isEnabled = (message.arg1 == 1);
596                         if (mTimeoutEnabled == isEnabled) {
597                             break;
598                         }
599                         mTimeoutEnabled = isEnabled;
600                         if (!mTimeoutEnabled) {
601                             cancelTimeoutMessage();
602                         }
603                         if (mTimeoutEnabled && mNumAssociatedStations == 0) {
604                             scheduleTimeoutMessage();
605                         }
606                         break;
607                     case CMD_INTERFACE_STATUS_CHANGED:
608                         boolean isUp = message.arg1 == 1;
609                         onUpChanged(isUp);
610                         break;
611                     case CMD_START:
612                         // Already started, ignore this command.
613                         break;
614                     case CMD_NO_ASSOCIATED_STATIONS_TIMEOUT:
615                         if (!mTimeoutEnabled) {
616                             Log.wtf(TAG, "Timeout message received while timeout is disabled."
617                                     + " Dropping.");
618                             break;
619                         }
620                         if (mNumAssociatedStations != 0) {
621                             Log.wtf(TAG, "Timeout message received but has clients. Dropping.");
622                             break;
623                         }
624                         Log.i(TAG, "Timeout message received. Stopping soft AP.");
625                         updateApState(WifiManager.WIFI_AP_STATE_DISABLING,
626                                 WifiManager.WIFI_AP_STATE_ENABLED, 0);
627                         transitionTo(mIdleState);
628                         break;
629                     case CMD_INTERFACE_DESTROYED:
630                         Log.d(TAG, "Interface was cleanly destroyed.");
631                         updateApState(WifiManager.WIFI_AP_STATE_DISABLING,
632                                 WifiManager.WIFI_AP_STATE_ENABLED, 0);
633                         mIfaceIsDestroyed = true;
634                         transitionTo(mIdleState);
635                         break;
636                     case CMD_FAILURE:
637                         Log.w(TAG, "hostapd failure, stop and report failure");
638                         /* fall through */
639                     case CMD_INTERFACE_DOWN:
640                         Log.w(TAG, "interface error, stop and report failure");
641                         updateApState(WifiManager.WIFI_AP_STATE_FAILED,
642                                 WifiManager.WIFI_AP_STATE_ENABLED,
643                                 WifiManager.SAP_START_FAILURE_GENERAL);
644                         updateApState(WifiManager.WIFI_AP_STATE_DISABLING,
645                                 WifiManager.WIFI_AP_STATE_FAILED, 0);
646                         transitionTo(mIdleState);
647                         break;
648                     default:
649                         return NOT_HANDLED;
650                 }
651                 return HANDLED;
652             }
653         }
654     }
655 }
656