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