1/*
2 * Copyright (C) 2015 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 */
16package com.android.settingslib.wifi;
17
18import android.content.Intent;
19import android.net.NetworkInfo;
20import android.net.NetworkInfo.State;
21import android.net.wifi.ScanResult;
22import android.net.wifi.WifiConfiguration;
23import android.net.wifi.WifiInfo;
24import android.net.wifi.WifiManager;
25import android.net.wifi.WifiSsid;
26import android.os.HandlerThread;
27import android.os.Looper;
28import android.util.Log;
29
30import com.android.settingslib.BaseTest;
31import com.android.settingslib.wifi.WifiTracker.Scanner;
32import com.android.settingslib.wifi.WifiTracker.WifiListener;
33
34import org.mockito.ArgumentCaptor;
35import org.mockito.Mockito;
36
37import java.io.PrintWriter;
38import java.io.StringWriter;
39import java.util.ArrayList;
40import java.util.List;
41
42public class WifiTrackerTest extends BaseTest {
43
44    private static final String TAG = "WifiTrackerTest";
45
46    private static final String[] TEST_SSIDS = new String[] {
47        "TEST_SSID_1",
48        "TEST_SSID_2",
49        "TEST_SSID_3",
50        "TEST_SSID_4",
51        "TEST_SSID_5",
52    };
53    private static final int NUM_NETWORKS = 5;
54
55    private WifiManager mWifiManager;
56    private WifiListener mWifiListener;
57
58    private WifiTracker mWifiTracker;
59
60    private HandlerThread mWorkerThread;
61    private Looper mLooper;
62    private HandlerThread mMainThread;
63    private Looper mMainLooper;
64
65    @Override
66    protected void setUp() throws Exception {
67        super.setUp();
68
69        mWifiManager = Mockito.mock(WifiManager.class);
70        mWifiListener = Mockito.mock(WifiListener.class);
71        mWorkerThread = new HandlerThread("TestHandlerThread");
72        mWorkerThread.start();
73        mLooper = mWorkerThread.getLooper();
74        mMainThread = new HandlerThread("TestHandlerThread");
75        mMainThread.start();
76        mMainLooper = mMainThread.getLooper();
77        mWifiTracker = new WifiTracker(mContext, mWifiListener, mLooper, true, true, true,
78                mWifiManager, mMainLooper);
79        mWifiTracker.mScanner = mWifiTracker.new Scanner();
80        Mockito.when(mWifiManager.isWifiEnabled()).thenReturn(true);
81    }
82
83    @Override
84    protected void tearDown() throws Exception {
85        StringWriter sw = new StringWriter();
86        PrintWriter pw = new PrintWriter(sw);
87        mWifiTracker.dump(pw);
88        pw.flush();
89        Log.d(TAG, sw.toString());
90        super.tearDown();
91    }
92
93    public void testAccessPointsCallback() {
94        sendScanResultsAndProcess(false);
95
96        Mockito.verify(mWifiListener, Mockito.atLeastOnce()).onAccessPointsChanged();
97    }
98
99    public void testConnectedCallback() {
100        sendConnected();
101        waitForThreads();
102
103        Mockito.verify(mWifiListener, Mockito.atLeastOnce()).onConnectedChanged();
104        assertEquals(true, mWifiTracker.isConnected());
105    }
106
107    public void testWifiStateCallback() {
108        final int TEST_WIFI_STATE = WifiManager.WIFI_STATE_ENABLED;
109
110        Intent i = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
111        i.putExtra(WifiManager.EXTRA_WIFI_STATE, TEST_WIFI_STATE);
112        mWifiTracker.mReceiver.onReceive(mContext, i);
113        waitForThreads();
114
115        ArgumentCaptor<Integer> wifiState = ArgumentCaptor.forClass(Integer.class);
116        Mockito.verify(mWifiListener, Mockito.atLeastOnce())
117                .onWifiStateChanged(wifiState.capture());
118        assertEquals(TEST_WIFI_STATE, (int) wifiState.getValue());
119    }
120
121    public void testScanner() {
122        // TODO: Figure out how to verify more of the Scanner functionality.
123        // Make scans be successful.
124        Mockito.when(mWifiManager.startScan()).thenReturn(true);
125
126        mWifiTracker.mScanner.handleMessage(mWifiTracker.mScanner.obtainMessage(Scanner.MSG_SCAN));
127        Mockito.verify(mWifiManager, Mockito.atLeastOnce()).startScan();
128    }
129
130    public void testNetworkSorting() {
131        List<WifiConfiguration> wifiConfigs = new ArrayList<WifiConfiguration>();
132        List<ScanResult> scanResults = new ArrayList<ScanResult>();
133        String[] expectedSsids = generateTestNetworks(wifiConfigs, scanResults, true);
134
135        // Tell WifiTracker we are connected now.
136        sendConnected();
137
138        // Send all of the configs and scan results to the tracker.
139        Mockito.when(mWifiManager.getConfiguredNetworks()).thenReturn(wifiConfigs);
140        Mockito.when(mWifiManager.getScanResults()).thenReturn(scanResults);
141        sendScanResultsAndProcess(false);
142
143        List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints();
144        assertEquals("Expected number of results", NUM_NETWORKS, accessPoints.size());
145        for (int i = 0; i < NUM_NETWORKS; i++) {
146            assertEquals("Verifying slot " + i, expectedSsids[i], accessPoints.get(i).getSsid());
147        }
148    }
149
150    public void testSavedOnly() {
151        mWifiTracker = new WifiTracker(mContext, mWifiListener, mLooper, true, false, true,
152                mWifiManager, mMainLooper);
153        mWifiTracker.mScanner = mWifiTracker.new Scanner();
154
155        List<WifiConfiguration> wifiConfigs = new ArrayList<WifiConfiguration>();
156        List<ScanResult> scanResults = new ArrayList<ScanResult>();
157        generateTestNetworks(wifiConfigs, scanResults, true);
158
159        // Tell WifiTracker we are connected now.
160        sendConnected();
161
162        // Send all of the configs and scan results to the tracker.
163        Mockito.when(mWifiManager.getConfiguredNetworks()).thenReturn(wifiConfigs);
164        Mockito.when(mWifiManager.getScanResults()).thenReturn(scanResults);
165        sendScanResultsAndProcess(false);
166
167        List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints();
168        // Only expect the first two to come back in the results.
169        assertEquals("Expected number of results", 2, accessPoints.size());
170        assertEquals(TEST_SSIDS[1], accessPoints.get(0).getSsid());
171        assertEquals(TEST_SSIDS[0], accessPoints.get(1).getSsid());
172    }
173
174    /**
175     * This tests the case where Settings runs this on a non-looper thread for indexing.
176     */
177    public void testSavedOnlyNoLooper() {
178        mWifiTracker = new WifiTracker(mContext, mWifiListener, mLooper, true, false, true,
179                mWifiManager,  null);
180        mWifiTracker.mScanner = mWifiTracker.new Scanner();
181
182        List<WifiConfiguration> wifiConfigs = new ArrayList<WifiConfiguration>();
183        List<ScanResult> scanResults = new ArrayList<ScanResult>();
184        generateTestNetworks(wifiConfigs, scanResults, true);
185
186        // Send all of the configs and scan results to the tracker.
187        Mockito.when(mWifiManager.getConfiguredNetworks()).thenReturn(wifiConfigs);
188        Mockito.when(mWifiManager.getScanResults()).thenReturn(scanResults);
189        mWifiTracker.forceUpdate();
190
191        List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints();
192        // Only expect the first two to come back in the results.
193        assertEquals("Expected number of results", 2, accessPoints.size());
194        assertEquals(TEST_SSIDS[1], accessPoints.get(0).getSsid());
195        assertEquals(TEST_SSIDS[0], accessPoints.get(1).getSsid());
196    }
197
198    public void testAvailableOnly() {
199        mWifiTracker = new WifiTracker(mContext, mWifiListener, mLooper, false, true, true,
200                mWifiManager, mMainLooper);
201        mWifiTracker.mScanner = mWifiTracker.new Scanner();
202
203        List<WifiConfiguration> wifiConfigs = new ArrayList<WifiConfiguration>();
204        List<ScanResult> scanResults = new ArrayList<ScanResult>();
205        String[] expectedSsids = generateTestNetworks(wifiConfigs, scanResults, true);
206
207        // Tell WifiTracker we are connected now.
208        sendConnected();
209
210        // Send all of the configs and scan results to the tracker.
211        Mockito.when(mWifiManager.getConfiguredNetworks()).thenReturn(wifiConfigs);
212        Mockito.when(mWifiManager.getScanResults()).thenReturn(scanResults);
213        sendScanResultsAndProcess(false);
214
215        // Expect the last one (sorted order) to be left off since its only saved.
216        List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints();
217        assertEquals("Expected number of results", NUM_NETWORKS - 1, accessPoints.size());
218        for (int i = 0; i < NUM_NETWORKS - 1; i++) {
219            assertEquals("Verifying slot " + i, expectedSsids[i], accessPoints.get(i).getSsid());
220        }
221    }
222
223    public void testNonEphemeralConnected() {
224        mWifiTracker = new WifiTracker(mContext, mWifiListener, mLooper, false, true, true,
225                mWifiManager, mMainLooper);
226        mWifiTracker.mScanner = mWifiTracker.new Scanner();
227
228        List<WifiConfiguration> wifiConfigs = new ArrayList<WifiConfiguration>();
229        List<ScanResult> scanResults = new ArrayList<ScanResult>();
230        generateTestNetworks(wifiConfigs, scanResults, false);
231
232        // Tell WifiTracker we are connected now.
233        sendConnected();
234
235        // Send all of the configs and scan results to the tracker.
236        Mockito.when(mWifiManager.getConfiguredNetworks()).thenReturn(wifiConfigs);
237        Mockito.when(mWifiManager.getScanResults()).thenReturn(scanResults);
238        // Do this twice to catch a bug that was happening in the caching, making things ephemeral.
239        sendScanResultsAndProcess(true);
240
241        List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints();
242        assertEquals("Expected number of results", NUM_NETWORKS - 1, accessPoints.size());
243        assertFalse("Connection is not ephemeral", accessPoints.get(0).isEphemeral());
244        assertTrue("Connected to wifi", accessPoints.get(0).isActive());
245    }
246
247    public void testEnableResumeScanning() {
248        mWifiTracker.mScanner = null;
249
250        Intent i = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
251        // Make sure disable/enable cycle works with no scanner (no crashing).
252        i.putExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_DISABLED);
253        mWifiTracker.mReceiver.onReceive(mContext, i);
254        i.putExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_ENABLED);
255        mWifiTracker.mReceiver.onReceive(mContext, i);
256
257        Mockito.when(mWifiManager.isWifiEnabled()).thenReturn(false);
258        i.putExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_DISABLED);
259        mWifiTracker.mReceiver.onReceive(mContext, i);
260        // Now enable scanning while wifi is off, it shouldn't start.
261        mWifiTracker.resumeScanning();
262        assertFalse(mWifiTracker.mScanner.isScanning());
263
264        // Turn on wifi and make sure scanning starts.
265        i.putExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_ENABLED);
266        mWifiTracker.mReceiver.onReceive(mContext, i);
267        assertTrue(mWifiTracker.mScanner.isScanning());
268    }
269
270    private String[] generateTestNetworks(List<WifiConfiguration> wifiConfigs,
271            List<ScanResult> scanResults, boolean connectedIsEphemeral) {
272        String[] expectedSsids = new String[NUM_NETWORKS];
273
274        // First is just saved;
275        addConfig(wifiConfigs, TEST_SSIDS[0]);
276        // This should come last since its not available.
277        expectedSsids[4] = TEST_SSIDS[0];
278
279        // Second is saved and available.
280        addConfig(wifiConfigs, TEST_SSIDS[1]);
281        addResult(scanResults, TEST_SSIDS[1], 0);
282        // This one is going to have a couple extra results, to verify de-duplication.
283        addResult(scanResults, TEST_SSIDS[1], 2);
284        addResult(scanResults, TEST_SSIDS[1], 1);
285        // This should come second since it is available and saved but not connected.
286        expectedSsids[1] = TEST_SSIDS[1];
287
288        // Third is just available, but higher rssi.
289        addResult(scanResults, TEST_SSIDS[2], 3);
290        // This comes after the next one since it has a lower rssi.
291        expectedSsids[3] = TEST_SSIDS[2];
292
293        // Fourth also just available but with even higher rssi.
294        addResult(scanResults, TEST_SSIDS[3], 4);
295        // This is the highest rssi but not saved so it should be after the saved+availables.
296        expectedSsids[2] = TEST_SSIDS[3];
297
298        // Last is going to be connected.
299        int netId = WifiConfiguration.INVALID_NETWORK_ID;
300        if (!connectedIsEphemeral) {
301            netId = addConfig(wifiConfigs, TEST_SSIDS[4]);
302        }
303        addResult(scanResults, TEST_SSIDS[4], 2);
304        // Setup wifi connection to be this one.
305        WifiInfo wifiInfo = Mockito.mock(WifiInfo.class);
306        Mockito.when(wifiInfo.getSSID()).thenReturn(TEST_SSIDS[4]);
307        Mockito.when(wifiInfo.getNetworkId()).thenReturn(netId);
308        Mockito.when(mWifiManager.getConnectionInfo()).thenReturn(wifiInfo);
309        // This should come first since it is connected.
310        expectedSsids[0] = TEST_SSIDS[4];
311
312        return expectedSsids;
313    }
314
315    private void addResult(List<ScanResult> results, String ssid, int level) {
316        results.add(new ScanResult(WifiSsid.createFromAsciiEncoded(ssid),
317                ssid, ssid, levelToRssi(level), AccessPoint.LOWER_FREQ_24GHZ, 0));
318    }
319
320    public static int levelToRssi(int level) {
321        // Reverse level to rssi calculation based off from WifiManager.calculateSignalLevel.
322        final int MAX_RSSI = -55;
323        final int MIN_RSSI = -100;
324        final int NUM_LEVELS = 4;
325        return level * (MAX_RSSI - MIN_RSSI) / (NUM_LEVELS - 1) + MIN_RSSI;
326    }
327
328    private int addConfig(List<WifiConfiguration> configs, String ssid) {
329        WifiConfiguration config = new WifiConfiguration();
330        config.networkId = configs.size();
331        config.SSID = '"' + ssid + '"';
332        configs.add(config);
333        return config.networkId;
334    }
335
336    private void sendConnected() {
337        NetworkInfo networkInfo = Mockito.mock(NetworkInfo.class);
338        Mockito.when(networkInfo.isConnected()).thenReturn(true);
339        Mockito.when(networkInfo.getState()).thenReturn(State.CONNECTED);
340        Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
341        intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
342        mWifiTracker.mReceiver.onReceive(mContext, intent);
343    }
344
345    private void sendScanResultsAndProcess(boolean sendTwice) {
346        Intent i = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
347        mWifiTracker.mReceiver.onReceive(mContext, i);
348        if (sendTwice) {
349            mWifiTracker.mReceiver.onReceive(mContext, i);
350        }
351        waitForThreads();
352    }
353
354    private void waitForThreads() {
355        // Run all processing.
356        mWorkerThread.quitSafely();
357        try {
358            mWorkerThread.join();
359        } catch (InterruptedException e) {
360        }
361        // Send all callbacks.
362        mMainThread.quitSafely();
363        try {
364            mMainThread.join();
365        } catch (InterruptedException e) {
366        }
367    }
368
369}
370