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.net.wifi.WifiManager;
     22 import android.os.Looper;
     23 import android.os.Message;
     24 import android.text.TextUtils;
     25 import android.util.Log;
     26 
     27 import com.android.internal.util.IState;
     28 import com.android.internal.util.State;
     29 import com.android.internal.util.StateMachine;
     30 import com.android.server.wifi.WifiNative.InterfaceCallback;
     31 
     32 import java.io.FileDescriptor;
     33 import java.io.PrintWriter;
     34 
     35 /**
     36  * Manager WiFi in Scan Only Mode - no network connections.
     37  */
     38 public class ScanOnlyModeManager implements ActiveModeManager {
     39 
     40     private final ScanOnlyModeStateMachine mStateMachine;
     41 
     42     private static final String TAG = "WifiScanOnlyModeManager";
     43 
     44     private final Context mContext;
     45     private final WifiNative mWifiNative;
     46 
     47     private final WifiMetrics mWifiMetrics;
     48     private final Listener mListener;
     49     private final ScanRequestProxy mScanRequestProxy;
     50     private final WakeupController mWakeupController;
     51 
     52     private String mClientInterfaceName;
     53     private boolean mIfaceIsUp = false;
     54 
     55     private boolean mExpectedStop = false;
     56 
     57     ScanOnlyModeManager(@NonNull Context context, @NonNull Looper looper,
     58                         @NonNull WifiNative wifiNative, @NonNull Listener listener,
     59                         @NonNull WifiMetrics wifiMetrics,
     60                         @NonNull ScanRequestProxy scanRequestProxy,
     61                         @NonNull WakeupController wakeupController) {
     62         mContext = context;
     63         mWifiNative = wifiNative;
     64         mListener = listener;
     65         mWifiMetrics = wifiMetrics;
     66         mScanRequestProxy = scanRequestProxy;
     67         mWakeupController = wakeupController;
     68         mStateMachine = new ScanOnlyModeStateMachine(looper);
     69     }
     70 
     71     /**
     72      * Start scan only mode.
     73      */
     74     public void start() {
     75         mStateMachine.sendMessage(ScanOnlyModeStateMachine.CMD_START);
     76     }
     77 
     78     /**
     79      * Cancel any pending scans and stop scan mode.
     80      */
     81     public void stop() {
     82         Log.d(TAG, " currentstate: " + getCurrentStateName());
     83         mExpectedStop = true;
     84         mStateMachine.quitNow();
     85     }
     86 
     87     /**
     88      * Dump info about this ScanOnlyMode manager.
     89      */
     90     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
     91         pw.println("--Dump of ScanOnlyModeManager--");
     92 
     93         pw.println("current StateMachine mode: " + getCurrentStateName());
     94         pw.println("mClientInterfaceName: " + mClientInterfaceName);
     95         pw.println("mIfaceIsUp: " + mIfaceIsUp);
     96     }
     97 
     98     /**
     99      * Listener for ScanOnlyMode state changes.
    100      */
    101     public interface Listener {
    102         /**
    103          * Invoke when wifi state changes.
    104          * @param state new wifi state
    105          */
    106         void onStateChanged(int state);
    107     }
    108 
    109     private String getCurrentStateName() {
    110         IState currentState = mStateMachine.getCurrentState();
    111 
    112         if (currentState != null) {
    113             return currentState.getName();
    114         }
    115 
    116         return "StateMachine not active";
    117     }
    118 
    119     /**
    120      * Update Wifi state.
    121      * @param state new Wifi state
    122      */
    123     private void updateWifiState(int state) {
    124         if (mExpectedStop) {
    125             Log.d(TAG, "expected stop, not triggering callbacks: state = " + state);
    126             return;
    127         }
    128 
    129         // Once we report the mode has stopped/failed any other stop signals are redundant
    130         // note: this can happen in failure modes where we get multiple callbacks as underlying
    131         // components/interface stops or the underlying interface is destroyed in cleanup
    132         if (state == WifiManager.WIFI_STATE_UNKNOWN || state == WifiManager.WIFI_STATE_DISABLED) {
    133             mExpectedStop = true;
    134         }
    135 
    136         mListener.onStateChanged(state);
    137     }
    138 
    139     private class ScanOnlyModeStateMachine extends StateMachine {
    140         // Commands for the state machine.
    141         public static final int CMD_START = 0;
    142         public static final int CMD_INTERFACE_STATUS_CHANGED = 3;
    143         public static final int CMD_INTERFACE_DESTROYED = 4;
    144         public static final int CMD_INTERFACE_DOWN = 5;
    145 
    146         private final State mIdleState = new IdleState();
    147         private final State mStartedState = new StartedState();
    148 
    149         private final InterfaceCallback mWifiNativeInterfaceCallback = new InterfaceCallback() {
    150             @Override
    151             public void onDestroyed(String ifaceName) {
    152                 if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
    153                     sendMessage(CMD_INTERFACE_DESTROYED);
    154                 }
    155             }
    156 
    157             @Override
    158             public void onUp(String ifaceName) {
    159                 if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
    160                     sendMessage(CMD_INTERFACE_STATUS_CHANGED, 1);
    161                 }
    162             }
    163 
    164             @Override
    165             public void onDown(String ifaceName) {
    166                 if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
    167                     sendMessage(CMD_INTERFACE_STATUS_CHANGED, 0);
    168                 }
    169             }
    170         };
    171 
    172         ScanOnlyModeStateMachine(Looper looper) {
    173             super(TAG, looper);
    174 
    175             addState(mIdleState);
    176             addState(mStartedState);
    177 
    178             setInitialState(mIdleState);
    179             start();
    180         }
    181 
    182         private class IdleState extends State {
    183 
    184             @Override
    185             public void enter() {
    186                 Log.d(TAG, "entering IdleState");
    187                 mClientInterfaceName = null;
    188             }
    189 
    190             @Override
    191             public boolean processMessage(Message message) {
    192                 switch (message.what) {
    193                     case CMD_START:
    194                         mClientInterfaceName = mWifiNative.setupInterfaceForClientMode(true,
    195                                 mWifiNativeInterfaceCallback);
    196                         if (TextUtils.isEmpty(mClientInterfaceName)) {
    197                             Log.e(TAG, "Failed to create ClientInterface. Sit in Idle");
    198                             updateWifiState(WifiManager.WIFI_STATE_UNKNOWN);
    199                             break;
    200                         }
    201                         // we have a new scanning interface, make sure scanner knows we aren't
    202                         // ready yet and clear out the ScanRequestProxy
    203                         sendScanAvailableBroadcast(false);
    204                         // explicitly disable scanning for hidden networks in case we were
    205                         // previously in client mode
    206                         mScanRequestProxy.enableScanningForHiddenNetworks(false);
    207                         mScanRequestProxy.clearScanResults();
    208 
    209                         transitionTo(mStartedState);
    210                         break;
    211                     default:
    212                         Log.d(TAG, "received an invalid message: " + message);
    213                         return NOT_HANDLED;
    214                 }
    215                 return HANDLED;
    216             }
    217         }
    218 
    219         private class StartedState extends State {
    220 
    221             private void onUpChanged(boolean isUp) {
    222                 if (isUp == mIfaceIsUp) {
    223                     return;  // no change
    224                 }
    225                 mIfaceIsUp = isUp;
    226                 if (isUp) {
    227                     Log.d(TAG, "Wifi is ready to use for scanning");
    228                     mWakeupController.start();
    229                     sendScanAvailableBroadcast(true);
    230                     updateWifiState(WifiManager.WIFI_STATE_ENABLED);
    231                 } else {
    232                     // if the interface goes down we should exit and go back to idle state.
    233                     Log.d(TAG, "interface down - stop scan mode");
    234                     mStateMachine.sendMessage(CMD_INTERFACE_DOWN);
    235                 }
    236             }
    237 
    238             @Override
    239             public void enter() {
    240                 Log.d(TAG, "entering StartedState");
    241                 mScanRequestProxy.enableScanningForHiddenNetworks(false);
    242 
    243                 mIfaceIsUp = false;
    244                 onUpChanged(mWifiNative.isInterfaceUp(mClientInterfaceName));
    245             }
    246 
    247             @Override
    248             public boolean processMessage(Message message) {
    249                 switch(message.what) {
    250                     case CMD_START:
    251                         // Already started, ignore this command.
    252                         break;
    253                     case CMD_INTERFACE_DESTROYED:
    254                         Log.d(TAG, "Interface cleanly destroyed, report scan mode stop.");
    255                         mClientInterfaceName = null;
    256                         transitionTo(mIdleState);
    257                         break;
    258                     case CMD_INTERFACE_STATUS_CHANGED:
    259                         boolean isUp = message.arg1 == 1;
    260                         onUpChanged(isUp);
    261                         break;
    262                     case CMD_INTERFACE_DOWN:
    263                         Log.d(TAG, "interface down!  stop mode");
    264                         updateWifiState(WifiManager.WIFI_STATE_UNKNOWN);
    265                         transitionTo(mIdleState);
    266                         break;
    267                     default:
    268                         return NOT_HANDLED;
    269                 }
    270                 return HANDLED;
    271             }
    272 
    273             /**
    274              * Clean up state and unregister listeners.
    275              */
    276             @Override
    277             public void exit() {
    278                 mWakeupController.stop();
    279                 if (mClientInterfaceName != null) {
    280                     mWifiNative.teardownInterface(mClientInterfaceName);
    281                     mClientInterfaceName = null;
    282                 }
    283                 updateWifiState(WifiManager.WIFI_STATE_DISABLED);
    284 
    285                 // once we leave started, nothing else to do...  stop the state machine
    286                 mStateMachine.quitNow();
    287             }
    288         }
    289     }
    290 
    291     private void sendScanAvailableBroadcast(boolean available) {
    292         sendScanAvailableBroadcast(mContext, available);
    293     }
    294 }
    295