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