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
17package com.android.server.wifi;
18
19import android.annotation.NonNull;
20import android.content.Context;
21import android.content.Intent;
22import android.net.wifi.WifiManager;
23import android.os.Looper;
24import android.os.Message;
25import android.os.UserHandle;
26import android.text.TextUtils;
27import android.util.Log;
28
29import com.android.internal.util.IState;
30import com.android.internal.util.State;
31import com.android.internal.util.StateMachine;
32import com.android.server.wifi.WifiNative.InterfaceCallback;
33
34import java.io.FileDescriptor;
35import java.io.PrintWriter;
36
37/**
38 * Manager WiFi in Client Mode where we connect to configured networks.
39 */
40public 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