• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package com.google.android.mobly.snippet.bundled;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.net.wifi.ScanResult;
24 import android.net.wifi.WifiConfiguration;
25 import android.net.wifi.WifiInfo;
26 import android.net.wifi.WifiManager;
27 import android.os.Build;
28 import androidx.annotation.Nullable;
29 import androidx.annotation.RequiresApi;
30 import androidx.test.platform.app.InstrumentationRegistry;
31 import com.google.android.mobly.snippet.Snippet;
32 import com.google.android.mobly.snippet.bundled.utils.JsonDeserializer;
33 import com.google.android.mobly.snippet.bundled.utils.JsonSerializer;
34 import com.google.android.mobly.snippet.bundled.utils.Utils;
35 import com.google.android.mobly.snippet.rpc.Rpc;
36 import com.google.android.mobly.snippet.rpc.RpcMinSdk;
37 import com.google.android.mobly.snippet.util.Log;
38 import java.lang.reflect.InvocationTargetException;
39 import java.lang.reflect.Method;
40 import java.util.ArrayList;
41 import java.util.List;
42 import org.json.JSONArray;
43 import org.json.JSONException;
44 import org.json.JSONObject;
45 import android.net.wifi.SupplicantState;
46 
47 import com.google.android.mobly.snippet.bundled.utils.Utils;
48 
49 /** Snippet class exposing Android APIs in WifiManager. */
50 public class WifiManagerSnippet implements Snippet {
51     private static class WifiManagerSnippetException extends Exception {
52         private static final long serialVersionUID = 1;
53 
WifiManagerSnippetException(String msg)54         public WifiManagerSnippetException(String msg) {
55             super(msg);
56         }
57     }
58 
59     private static final int TIMEOUT_TOGGLE_STATE = 30;
60     private final WifiManager mWifiManager;
61     private final Context mContext;
62     private final JsonSerializer mJsonSerializer = new JsonSerializer();
63     private volatile boolean mIsScanResultAvailable = false;
64 
WifiManagerSnippet()65     public WifiManagerSnippet() throws Throwable {
66         mContext = InstrumentationRegistry.getInstrumentation().getContext();
67         mWifiManager =
68                 (WifiManager)
69                         mContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
70         Utils.adaptShellPermissionIfRequired(mContext);
71     }
72 
73     @Rpc(
74             description =
75                     "Clears all configured networks. This will only work if all configured "
76                             + "networks were added through this MBS instance")
wifiClearConfiguredNetworks()77     public void wifiClearConfiguredNetworks() throws WifiManagerSnippetException {
78         List<WifiConfiguration> unremovedConfigs = mWifiManager.getConfiguredNetworks();
79         List<WifiConfiguration> failedConfigs = new ArrayList<>();
80         if (unremovedConfigs == null) {
81             throw new WifiManagerSnippetException(
82                     "Failed to get a list of configured networks. Is wifi disabled?");
83         }
84         for (WifiConfiguration config : unremovedConfigs) {
85             if (!mWifiManager.removeNetwork(config.networkId)) {
86                 failedConfigs.add(config);
87             }
88         }
89 
90         // If removeNetwork is called on a network with both an open and OWE config, it will remove
91         // both. The subsequent call on the same network will fail. The clear operation may succeed
92         // even if failures appear in the log below.
93         if (!failedConfigs.isEmpty()) {
94             Log.e("Encountered error while removing networks: " + failedConfigs);
95         }
96 
97         // Re-check configured configs list to ensure that it is cleared
98         unremovedConfigs = mWifiManager.getConfiguredNetworks();
99         if (!unremovedConfigs.isEmpty()) {
100             throw new WifiManagerSnippetException("Failed to remove networks: " + unremovedConfigs);
101         }
102     }
103 
104     @Rpc(description = "Turns on Wi-Fi with a 30s timeout.")
wifiEnable()105     public void wifiEnable() throws InterruptedException, WifiManagerSnippetException {
106         if (mWifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLED) {
107             return;
108         }
109         // If Wi-Fi is trying to turn off, wait for that to complete before continuing.
110         if (mWifiManager.getWifiState() == WifiManager.WIFI_STATE_DISABLING) {
111             if (!Utils.waitUntil(
112                     () -> mWifiManager.getWifiState() == WifiManager.WIFI_STATE_DISABLED,
113                     TIMEOUT_TOGGLE_STATE)) {
114                 Log.e(String.format("Wi-Fi failed to stabilize after %ss.", TIMEOUT_TOGGLE_STATE));
115             }
116         }
117         if (!mWifiManager.setWifiEnabled(true)) {
118             throw new WifiManagerSnippetException("Failed to initiate enabling Wi-Fi.");
119         }
120         if (!Utils.waitUntil(
121                 () -> mWifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLED,
122                 TIMEOUT_TOGGLE_STATE)) {
123             throw new WifiManagerSnippetException(
124                     String.format(
125                             "Failed to enable Wi-Fi after %ss, timeout!", TIMEOUT_TOGGLE_STATE));
126         }
127     }
128 
129     @Rpc(description = "Turns off Wi-Fi with a 30s timeout.")
wifiDisable()130     public void wifiDisable() throws InterruptedException, WifiManagerSnippetException {
131         if (mWifiManager.getWifiState() == WifiManager.WIFI_STATE_DISABLED) {
132             return;
133         }
134         // If Wi-Fi is trying to turn on, wait for that to complete before continuing.
135         if (mWifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLING) {
136             if (!Utils.waitUntil(
137                     () -> mWifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLED,
138                     TIMEOUT_TOGGLE_STATE)) {
139                 Log.e(String.format("Wi-Fi failed to stabilize after %ss.", TIMEOUT_TOGGLE_STATE));
140             }
141         }
142         if (!mWifiManager.setWifiEnabled(false)) {
143             throw new WifiManagerSnippetException("Failed to initiate disabling Wi-Fi.");
144         }
145         if (!Utils.waitUntil(
146                 () -> mWifiManager.getWifiState() == WifiManager.WIFI_STATE_DISABLED,
147                 TIMEOUT_TOGGLE_STATE)) {
148             throw new WifiManagerSnippetException(
149                     String.format(
150                             "Failed to disable Wi-Fi after %ss, timeout!", TIMEOUT_TOGGLE_STATE));
151         }
152     }
153 
154     @Rpc(description = "Checks if Wi-Fi is enabled.")
wifiIsEnabled()155     public boolean wifiIsEnabled() {
156         return mWifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLED;
157     }
158 
159     @Rpc(description = "Trigger Wi-Fi scan.")
wifiStartScan()160     public void wifiStartScan() throws WifiManagerSnippetException {
161         if (!mWifiManager.startScan()) {
162             throw new WifiManagerSnippetException("Failed to initiate Wi-Fi scan.");
163         }
164     }
165 
166     @Rpc(
167             description =
168                     "Get Wi-Fi scan results, which is a list of serialized WifiScanResult objects.")
wifiGetCachedScanResults()169     public JSONArray wifiGetCachedScanResults() throws JSONException {
170         JSONArray results = new JSONArray();
171         for (ScanResult result : mWifiManager.getScanResults()) {
172             results.put(mJsonSerializer.toJson(result));
173         }
174         return results;
175     }
176 
177     @Rpc(
178             description =
179                     "Start scan, wait for scan to complete, and return results, which is a list of "
180                             + "serialized WifiScanResult objects.")
wifiScanAndGetResults()181     public JSONArray wifiScanAndGetResults()
182             throws InterruptedException, JSONException, WifiManagerSnippetException {
183         mContext.registerReceiver(
184                 new WifiScanReceiver(),
185                 new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
186         wifiStartScan();
187         mIsScanResultAvailable = false;
188         if (!Utils.waitUntil(() -> mIsScanResultAvailable, 2 * 60)) {
189             throw new WifiManagerSnippetException(
190                     "Failed to get scan results after 2min, timeout!");
191         }
192         return wifiGetCachedScanResults();
193     }
194 
195     @Rpc(
196             description =
197                     "Connects to a Wi-Fi network. This covers the common network types like open and "
198                             + "WPA2.")
wifiConnectSimple(String ssid, @Nullable String password)199     public void wifiConnectSimple(String ssid, @Nullable String password)
200             throws InterruptedException, JSONException, WifiManagerSnippetException {
201         JSONObject config = new JSONObject();
202         config.put("SSID", ssid);
203         if (password != null) {
204             config.put("password", password);
205         }
206         wifiConnect(config);
207     }
208 
209     /**
210      * Gets the {@link WifiConfiguration} of a Wi-Fi network that has already been configured.
211      *
212      * <p>If the network has not been configured, returns null.
213      *
214      * <p>A network is configured if a WifiConfiguration was created for it and added with {@link
215      * WifiManager#addNetwork(WifiConfiguration)}.
216      */
getExistingConfiguredNetwork(String ssid)217     private WifiConfiguration getExistingConfiguredNetwork(String ssid) {
218         List<WifiConfiguration> wifiConfigs = mWifiManager.getConfiguredNetworks();
219         if (wifiConfigs == null) {
220             return null;
221         }
222         for (WifiConfiguration config : wifiConfigs) {
223             if (config.SSID.equals(ssid)) {
224                 return config;
225             }
226         }
227         return null;
228     }
229     /**
230      * Connect to a Wi-Fi network.
231      *
232      * @param wifiNetworkConfig A JSON object that contains the info required to connect to a Wi-Fi
233      *     network. It follows the fields of WifiConfiguration type, e.g. {"SSID": "myWifi",
234      *     "password": "12345678"}.
235      * @throws InterruptedException
236      * @throws JSONException
237      * @throws WifiManagerSnippetException
238      */
239     @Rpc(description = "Connects to a Wi-Fi network.")
wifiConnect(JSONObject wifiNetworkConfig)240     public void wifiConnect(JSONObject wifiNetworkConfig)
241             throws InterruptedException, JSONException, WifiManagerSnippetException {
242         Log.d("Got network config: " + wifiNetworkConfig);
243         WifiConfiguration wifiConfig = JsonDeserializer.jsonToWifiConfig(wifiNetworkConfig);
244         String SSID = wifiConfig.SSID;
245         // Return directly if network is already connected.
246         WifiInfo connectionInfo = mWifiManager.getConnectionInfo();
247         if (connectionInfo.getNetworkId() != -1
248                 && connectionInfo.getSSID().equals(wifiConfig.SSID)) {
249             Log.d("Network " + connectionInfo.getSSID() + " is already connected.");
250             return;
251         }
252         int networkId;
253         // If this is a network with a known SSID, connect with the existing config.
254         // We have to do this because in N+, network configs can only be modified by the UID that
255         // created the network. So any attempt to modify a network config that does not belong to us
256         // would result in error.
257         WifiConfiguration existingConfig = getExistingConfiguredNetwork(wifiConfig.SSID);
258         if (existingConfig != null) {
259             Log.w(
260                     "Connecting to network \""
261                             + existingConfig.SSID
262                             + "\" with its existing configuration: "
263                             + existingConfig.toString());
264             wifiConfig = existingConfig;
265             networkId = wifiConfig.networkId;
266         } else {
267             // If this is a network with a new SSID, add the network.
268             networkId = mWifiManager.addNetwork(wifiConfig);
269         }
270         mWifiManager.disconnect();
271         if (!mWifiManager.enableNetwork(networkId, true)) {
272             throw new WifiManagerSnippetException(
273                     "Failed to enable Wi-Fi network of ID: " + networkId);
274         }
275         if (!mWifiManager.reconnect()) {
276             throw new WifiManagerSnippetException(
277                     "Failed to reconnect to Wi-Fi network of ID: " + networkId);
278         }
279         if (!Utils.waitUntil(
280             () ->
281                 mWifiManager.getConnectionInfo().getSSID().equals(SSID)
282                     && mWifiManager.getConnectionInfo().getNetworkId() != -1 && mWifiManager
283                     .getConnectionInfo().getSupplicantState().equals(SupplicantState.COMPLETED),
284             90)) {
285             throw new WifiManagerSnippetException(
286                 String.format(
287                     "Failed to connect to '%s', timeout! Current connection: '%s'",
288                     wifiNetworkConfig, mWifiManager.getConnectionInfo().getSSID()));
289         }
290         Log.d(
291                 "Connected to network '"
292                         + mWifiManager.getConnectionInfo().getSSID()
293                         + "' with ID "
294                         + mWifiManager.getConnectionInfo().getNetworkId());
295     }
296 
297     @Rpc(
298             description =
299                     "Forget a configured Wi-Fi network by its network ID, which is part of the"
300                             + " WifiConfiguration.")
wifiRemoveNetwork(Integer networkId)301     public void wifiRemoveNetwork(Integer networkId) throws WifiManagerSnippetException {
302         if (!mWifiManager.removeNetwork(networkId)) {
303             throw new WifiManagerSnippetException("Failed to remove network of ID: " + networkId);
304         }
305     }
306 
307     @Rpc(
308             description =
309                     "Get the list of configured Wi-Fi networks, each is a serialized "
310                             + "WifiConfiguration object.")
wifiGetConfiguredNetworks()311     public List<JSONObject> wifiGetConfiguredNetworks() throws JSONException {
312         List<JSONObject> networks = new ArrayList<>();
313         for (WifiConfiguration config : mWifiManager.getConfiguredNetworks()) {
314             networks.add(mJsonSerializer.toJson(config));
315         }
316         return networks;
317     }
318 
319     @RpcMinSdk(Build.VERSION_CODES.LOLLIPOP)
320     @Rpc(description = "Enable or disable wifi verbose logging.")
wifiSetVerboseLogging(boolean enable)321     public void wifiSetVerboseLogging(boolean enable) throws Throwable {
322         Utils.invokeByReflection(mWifiManager, "enableVerboseLogging", enable ? 1 : 0);
323     }
324 
325     @Rpc(
326             description =
327                     "Get the information about the active Wi-Fi connection, which is a serialized "
328                             + "WifiInfo object.")
wifiGetConnectionInfo()329     public JSONObject wifiGetConnectionInfo() throws JSONException {
330         return mJsonSerializer.toJson(mWifiManager.getConnectionInfo());
331     }
332 
333     @Rpc(
334             description =
335                     "Get the info from last successful DHCP request, which is a serialized DhcpInfo "
336                             + "object.")
wifiGetDhcpInfo()337     public JSONObject wifiGetDhcpInfo() throws JSONException {
338         return mJsonSerializer.toJson(mWifiManager.getDhcpInfo());
339     }
340 
341     @Rpc(description = "Check whether Wi-Fi Soft AP (hotspot) is enabled.")
wifiIsApEnabled()342     public boolean wifiIsApEnabled() throws Throwable {
343         return (boolean) Utils.invokeByReflection(mWifiManager, "isWifiApEnabled");
344     }
345 
346     @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
347     @RpcMinSdk(Build.VERSION_CODES.LOLLIPOP)
348     @Rpc(
349             description =
350                     "Check whether this device supports 5 GHz band Wi-Fi. "
351                             + "Turn on Wi-Fi before calling.")
wifiIs5GHzBandSupported()352     public boolean wifiIs5GHzBandSupported() {
353         return mWifiManager.is5GHzBandSupported();
354     }
355 
356     /**
357      * Enable Wi-Fi Soft AP (hotspot).
358      *
359      * @param configuration The same format as the param wifiNetworkConfig param for wifiConnect.
360      * @throws Throwable
361      */
362     @Rpc(description = "Enable Wi-Fi Soft AP (hotspot).")
wifiEnableSoftAp(@ullable JSONObject configuration)363     public void wifiEnableSoftAp(@Nullable JSONObject configuration) throws Throwable {
364         // If no configuration is provided, the existing configuration would be used.
365         WifiConfiguration wifiConfiguration = null;
366         if (configuration != null) {
367             wifiConfiguration = JsonDeserializer.jsonToWifiConfig(configuration);
368             // Have to trim off the extra quotation marks since Soft AP logic interprets
369             // WifiConfiguration.SSID literally, unlike the WifiManager connection logic.
370             wifiConfiguration.SSID = JsonSerializer.trimQuotationMarks(wifiConfiguration.SSID);
371         }
372         if (!(boolean)
373                 Utils.invokeByReflection(
374                         mWifiManager, "setWifiApEnabled", wifiConfiguration, true)) {
375             throw new WifiManagerSnippetException("Failed to initiate turning on Wi-Fi Soft AP.");
376         }
377         if (!Utils.waitUntil(() -> wifiIsApEnabled() == true, 60)) {
378             throw new WifiManagerSnippetException(
379                     "Timed out after 60s waiting for Wi-Fi Soft AP state to turn on with configuration: "
380                             + configuration);
381         }
382     }
383 
384     /** Disables Wi-Fi Soft AP (hotspot). */
385     @Rpc(description = "Disable Wi-Fi Soft AP (hotspot).")
wifiDisableSoftAp()386     public void wifiDisableSoftAp() throws Throwable {
387         if (!(boolean)
388                 Utils.invokeByReflection(
389                         mWifiManager,
390                         "setWifiApEnabled",
391                         null /* No configuration needed for disabling */,
392                         false)) {
393             throw new WifiManagerSnippetException("Failed to initiate turning off Wi-Fi Soft AP.");
394         }
395         if (!Utils.waitUntil(() -> wifiIsApEnabled() == false, 60)) {
396             throw new WifiManagerSnippetException(
397                     "Timed out after 60s waiting for Wi-Fi Soft AP state to turn off.");
398         }
399     }
400 
401     @Override
shutdown()402     public void shutdown() {}
403 
404 
405     private class WifiScanReceiver extends BroadcastReceiver {
406 
407         @Override
onReceive(Context c, Intent intent)408         public void onReceive(Context c, Intent intent) {
409             String action = intent.getAction();
410             if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
411                 mIsScanResultAvailable = true;
412             }
413         }
414     }
415 }
416