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 android.annotation.NonNull;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.net.wifi.WifiManager;
     23 import android.os.Looper;
     24 import android.os.Message;
     25 import android.os.UserHandle;
     26 import android.text.TextUtils;
     27 import android.util.Log;
     28 
     29 import com.android.internal.util.IState;
     30 import com.android.internal.util.State;
     31 import com.android.internal.util.StateMachine;
     32 import com.android.server.wifi.WifiNative.InterfaceCallback;
     33 
     34 import java.io.FileDescriptor;
     35 import java.io.PrintWriter;
     36 
     37 /**
     38  * Manager WiFi in Client Mode where we connect to configured networks.
     39  */
     40 public class ClientModeManager implements ActiveModeManager {
     41     private static final String TAG = "WifiClientModeManager";
     42 
     43     private final ClientModeStateMachine mStateMachine;
     44 
     45     private final Context mContext;
     46     private final WifiNative mWifiNative;
     47 
     48     private final WifiMetrics mWifiMetrics;
     49     private final Listener mListener;
     50     private final ScanRequestProxy mScanRequestProxy;
     51     private final WifiStateMachine mWifiStateMachine;
     52 
     53     private String mClientInterfaceName;
     54     private boolean mIfaceIsUp = false;
     55 
     56     private boolean mExpectedStop = false;
     57 
     58     ClientModeManager(Context context, @NonNull Looper looper, WifiNative wifiNative,
     59             Listener listener, WifiMetrics wifiMetrics, ScanRequestProxy scanRequestProxy,
     60             WifiStateMachine wifiStateMachine) {
     61         mContext = context;
     62         mWifiNative = wifiNative;
     63         mListener = listener;
     64         mWifiMetrics = wifiMetrics;
     65         mScanRequestProxy = scanRequestProxy;
     66         mWifiStateMachine = wifiStateMachine;
     67         mStateMachine = new ClientModeStateMachine(looper);
     68     }
     69 
     70     /**
     71      * Start client mode.
     72      */
     73     public void start() {
     74         mStateMachine.sendMessage(ClientModeStateMachine.CMD_START);
     75     }
     76 
     77     /**
     78      * Disconnect from any currently connected networks and stop client mode.
     79      */
     80     public void stop() {
     81         Log.d(TAG, " currentstate: " + getCurrentStateName());
     82         mExpectedStop = true;
     83         if (mClientInterfaceName != null) {
     84             if (mIfaceIsUp) {
     85                 updateWifiState(WifiManager.WIFI_STATE_DISABLING,
     86                                 WifiManager.WIFI_STATE_ENABLED);
     87             } else {
     88                 updateWifiState(WifiManager.WIFI_STATE_DISABLING,
     89                                 WifiManager.WIFI_STATE_ENABLING);
     90             }
     91         }
     92         mStateMachine.quitNow();
     93     }
     94 
     95     /**
     96      * Dump info about this ClientMode manager.
     97      */
     98     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
     99         pw.println("--Dump of ClientModeManager--");
    100 
    101         pw.println("current StateMachine mode: " + getCurrentStateName());
    102         pw.println("mClientInterfaceName: " + mClientInterfaceName);
    103         pw.println("mIfaceIsUp: " + mIfaceIsUp);
    104     }
    105 
    106     /**
    107      * Listener for ClientMode state changes.
    108      */
    109     public interface Listener {
    110         /**
    111          * Invoke when wifi state changes.
    112          * @param state new wifi state
    113          */
    114         void onStateChanged(int state);
    115     }
    116 
    117     private String getCurrentStateName() {
    118         IState currentState = mStateMachine.getCurrentState();
    119 
    120         if (currentState != null) {
    121             return currentState.getName();
    122         }
    123 
    124         return "StateMachine not active";
    125     }
    126 
    127     /**
    128      * Update Wifi state and send the broadcast.
    129      * @param newState new Wifi state
    130      * @param currentState current wifi state
    131      */
    132     private void updateWifiState(int newState, int currentState) {
    133         if (!mExpectedStop) {
    134             mListener.onStateChanged(newState);
    135         } else {
    136             Log.d(TAG, "expected stop, not triggering callbacks: newState = " + newState);
    137         }
    138 
    139         // Once we report the mode has stopped/failed any other stop signals are redundant
    140         // note: this can happen in failure modes where we get multiple callbacks as underlying
    141         // components/interface stops or the underlying interface is destroyed in cleanup
    142         if (newState == WifiManager.WIFI_STATE_UNKNOWN
    143                 || newState == WifiManager.WIFI_STATE_DISABLED) {
    144             mExpectedStop = true;
    145         }
    146 
    147         if (newState == WifiManager.WIFI_STATE_UNKNOWN) {
    148             // do not need to broadcast failure to system
    149             return;
    150         }
    151 
    152         mWifiStateMachine.setWifiStateForApiCalls(newState);
    153 
    154         final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
    155         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    156         intent.putExtra(WifiManager.EXTRA_WIFI_STATE, newState);
    157         intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, currentState);
    158         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
    159     }
    160 
    161     private class ClientModeStateMachine extends StateMachine {
    162         // Commands for the state machine.
    163         public static final int CMD_START = 0;
    164         public static final int CMD_INTERFACE_STATUS_CHANGED = 3;
    165         public static final int CMD_INTERFACE_DESTROYED = 4;
    166         public static final int CMD_INTERFACE_DOWN = 5;
    167         private final State mIdleState = new IdleState();
    168         private final State mStartedState = new StartedState();
    169 
    170         private final InterfaceCallback mWifiNativeInterfaceCallback = new InterfaceCallback() {
    171             @Override
    172             public void onDestroyed(String ifaceName) {
    173                 if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
    174                     Log.d(TAG, "STA iface " + ifaceName + " was destroyed, stopping client mode");
    175 
    176                     // we must immediately clean up state in WSM to unregister all client mode
    177                     // related objects
    178                     // Note: onDestroyed is only called from the WSM thread
    179                     mWifiStateMachine.handleIfaceDestroyed();
    180 
    181                     sendMessage(CMD_INTERFACE_DESTROYED);
    182                 }
    183             }
    184 
    185             @Override
    186             public void onUp(String ifaceName) {
    187                 if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
    188                     sendMessage(CMD_INTERFACE_STATUS_CHANGED, 1);
    189                 }
    190             }
    191 
    192             @Override
    193             public void onDown(String ifaceName) {
    194                 if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
    195                     sendMessage(CMD_INTERFACE_STATUS_CHANGED, 0);
    196                 }
    197             }
    198         };
    199 
    200         ClientModeStateMachine(Looper looper) {
    201             super(TAG, looper);
    202 
    203             addState(mIdleState);
    204             addState(mStartedState);
    205 
    206             setInitialState(mIdleState);
    207             start();
    208         }
    209 
    210         private class IdleState extends State {
    211 
    212             @Override
    213             public void enter() {
    214                 Log.d(TAG, "entering IdleState");
    215                 mClientInterfaceName = null;
    216                 mIfaceIsUp = false;
    217             }
    218 
    219             @Override
    220             public boolean processMessage(Message message) {
    221                 switch (message.what) {
    222                     case CMD_START:
    223                         updateWifiState(WifiManager.WIFI_STATE_ENABLING,
    224                                         WifiManager.WIFI_STATE_DISABLED);
    225 
    226                         mClientInterfaceName = mWifiNative.setupInterfaceForClientMode(
    227                                 false /* not low priority */, mWifiNativeInterfaceCallback);
    228                         if (TextUtils.isEmpty(mClientInterfaceName)) {
    229                             Log.e(TAG, "Failed to create ClientInterface. Sit in Idle");
    230                             updateWifiState(WifiManager.WIFI_STATE_UNKNOWN,
    231                                             WifiManager.WIFI_STATE_ENABLING);
    232                             updateWifiState(WifiManager.WIFI_STATE_DISABLED,
    233                                             WifiManager.WIFI_STATE_UNKNOWN);
    234                             break;
    235                         }
    236                         sendScanAvailableBroadcast(false);
    237                         mScanRequestProxy.enableScanningForHiddenNetworks(false);
    238                         mScanRequestProxy.clearScanResults();
    239                         transitionTo(mStartedState);
    240                         break;
    241                     default:
    242                         Log.d(TAG, "received an invalid message: " + message);
    243                         return NOT_HANDLED;
    244                 }
    245                 return HANDLED;
    246             }
    247         }
    248 
    249         private class StartedState extends State {
    250 
    251             private void onUpChanged(boolean isUp) {
    252                 if (isUp == mIfaceIsUp) {
    253                     return;  // no change
    254                 }
    255                 mIfaceIsUp = isUp;
    256                 if (isUp) {
    257                     Log.d(TAG, "Wifi is ready to use for client mode");
    258                     sendScanAvailableBroadcast(true);
    259                     mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE,
    260                                                          mClientInterfaceName);
    261                     updateWifiState(WifiManager.WIFI_STATE_ENABLED,
    262                                     WifiManager.WIFI_STATE_ENABLING);
    263                 } else {
    264                     if (mWifiStateMachine.isConnectedMacRandomizationEnabled()) {
    265                         // Handle the error case where our underlying interface went down if we
    266                         // do not have mac randomization enabled (b/72459123).
    267                         return;
    268                     }
    269                     // if the interface goes down we should exit and go back to idle state.
    270                     Log.d(TAG, "interface down!");
    271                     updateWifiState(WifiManager.WIFI_STATE_UNKNOWN,
    272                                     WifiManager.WIFI_STATE_ENABLED);
    273                     mStateMachine.sendMessage(CMD_INTERFACE_DOWN);
    274                 }
    275             }
    276 
    277             @Override
    278             public void enter() {
    279                 Log.d(TAG, "entering StartedState");
    280                 mIfaceIsUp = false;
    281                 onUpChanged(mWifiNative.isInterfaceUp(mClientInterfaceName));
    282                 mScanRequestProxy.enableScanningForHiddenNetworks(true);
    283             }
    284 
    285             @Override
    286             public boolean processMessage(Message message) {
    287                 switch(message.what) {
    288                     case CMD_START:
    289                         // Already started, ignore this command.
    290                         break;
    291                     case CMD_INTERFACE_DOWN:
    292                         Log.e(TAG, "Detected an interface down, reporting failure to SelfRecovery");
    293                         mWifiStateMachine.failureDetected(SelfRecovery.REASON_STA_IFACE_DOWN);
    294 
    295                         updateWifiState(WifiManager.WIFI_STATE_DISABLING,
    296                                         WifiManager.WIFI_STATE_UNKNOWN);
    297                         transitionTo(mIdleState);
    298                         break;
    299                     case CMD_INTERFACE_STATUS_CHANGED:
    300                         boolean isUp = message.arg1 == 1;
    301                         onUpChanged(isUp);
    302                         break;
    303                     case CMD_INTERFACE_DESTROYED:
    304                         Log.d(TAG, "interface destroyed - client mode stopping");
    305 
    306                         updateWifiState(WifiManager.WIFI_STATE_DISABLING,
    307                                         WifiManager.WIFI_STATE_ENABLED);
    308                         mClientInterfaceName = null;
    309                         transitionTo(mIdleState);
    310                         break;
    311                     default:
    312                         return NOT_HANDLED;
    313                 }
    314                 return HANDLED;
    315             }
    316 
    317             /**
    318              * Clean up state, unregister listeners and update wifi state.
    319              */
    320             @Override
    321             public void exit() {
    322                 mWifiStateMachine.setOperationalMode(WifiStateMachine.DISABLED_MODE, null);
    323 
    324                 if (mClientInterfaceName != null) {
    325                     mWifiNative.teardownInterface(mClientInterfaceName);
    326                     mClientInterfaceName = null;
    327                     mIfaceIsUp = false;
    328                 }
    329 
    330                 updateWifiState(WifiManager.WIFI_STATE_DISABLED,
    331                                 WifiManager.WIFI_STATE_DISABLING);
    332 
    333                 // once we leave started, nothing else to do...  stop the state machine
    334                 mStateMachine.quitNow();
    335             }
    336         }
    337 
    338         private void sendScanAvailableBroadcast(boolean available) {
    339             Log.d(TAG, "sending scan available broadcast: " + available);
    340             final Intent intent = new Intent(WifiManager.WIFI_SCAN_AVAILABLE);
    341             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    342             if (available) {
    343                 intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WifiManager.WIFI_STATE_ENABLED);
    344             } else {
    345                 intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WifiManager.WIFI_STATE_DISABLED);
    346             }
    347             mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
    348         }
    349     }
    350 }
    351