blob: e457dc33193921c2d3ea75c8d266d34220680f85 [file] [log] [blame]
/*
* Copyright (C) 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.mobly.snippet.bundled;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.test.platform.app.InstrumentationRegistry;
import com.google.android.mobly.snippet.Snippet;
import com.google.android.mobly.snippet.bundled.utils.JsonDeserializer;
import com.google.android.mobly.snippet.bundled.utils.JsonSerializer;
import com.google.android.mobly.snippet.bundled.utils.Utils;
import com.google.android.mobly.snippet.rpc.Rpc;
import com.google.android.mobly.snippet.rpc.RpcMinSdk;
import com.google.android.mobly.snippet.util.Log;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.net.wifi.SupplicantState;
import com.google.android.mobly.snippet.bundled.utils.Utils;
/** Snippet class exposing Android APIs in WifiManager. */
public class WifiManagerSnippet implements Snippet {
private static class WifiManagerSnippetException extends Exception {
private static final long serialVersionUID = 1;
public WifiManagerSnippetException(String msg) {
super(msg);
}
}
private static final int TIMEOUT_TOGGLE_STATE = 30;
private final WifiManager mWifiManager;
private final Context mContext;
private final JsonSerializer mJsonSerializer = new JsonSerializer();
private volatile boolean mIsScanResultAvailable = false;
public WifiManagerSnippet() throws Throwable {
mContext = InstrumentationRegistry.getInstrumentation().getContext();
mWifiManager =
(WifiManager)
mContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
Utils.adaptShellPermissionIfRequired(mContext);
}
@Rpc(
description =
"Clears all configured networks. This will only work if all configured "
+ "networks were added through this MBS instance")
public void wifiClearConfiguredNetworks() throws WifiManagerSnippetException {
List<WifiConfiguration> unremovedConfigs = mWifiManager.getConfiguredNetworks();
List<WifiConfiguration> failedConfigs = new ArrayList<>();
if (unremovedConfigs == null) {
throw new WifiManagerSnippetException(
"Failed to get a list of configured networks. Is wifi disabled?");
}
for (WifiConfiguration config : unremovedConfigs) {
if (!mWifiManager.removeNetwork(config.networkId)) {
failedConfigs.add(config);
}
}
// If removeNetwork is called on a network with both an open and OWE config, it will remove
// both. The subsequent call on the same network will fail. The clear operation may succeed
// even if failures appear in the log below.
if (!failedConfigs.isEmpty()) {
Log.e("Encountered error while removing networks: " + failedConfigs);
}
// Re-check configured configs list to ensure that it is cleared
unremovedConfigs = mWifiManager.getConfiguredNetworks();
if (!unremovedConfigs.isEmpty()) {
throw new WifiManagerSnippetException("Failed to remove networks: " + unremovedConfigs);
}
}
@Rpc(description = "Turns on Wi-Fi with a 30s timeout.")
public void wifiEnable() throws InterruptedException, WifiManagerSnippetException {
if (mWifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLED) {
return;
}
// If Wi-Fi is trying to turn off, wait for that to complete before continuing.
if (mWifiManager.getWifiState() == WifiManager.WIFI_STATE_DISABLING) {
if (!Utils.waitUntil(
() -> mWifiManager.getWifiState() == WifiManager.WIFI_STATE_DISABLED,
TIMEOUT_TOGGLE_STATE)) {
Log.e(String.format("Wi-Fi failed to stabilize after %ss.", TIMEOUT_TOGGLE_STATE));
}
}
if (!mWifiManager.setWifiEnabled(true)) {
throw new WifiManagerSnippetException("Failed to initiate enabling Wi-Fi.");
}
if (!Utils.waitUntil(
() -> mWifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLED,
TIMEOUT_TOGGLE_STATE)) {
throw new WifiManagerSnippetException(
String.format(
"Failed to enable Wi-Fi after %ss, timeout!", TIMEOUT_TOGGLE_STATE));
}
}
@Rpc(description = "Turns off Wi-Fi with a 30s timeout.")
public void wifiDisable() throws InterruptedException, WifiManagerSnippetException {
if (mWifiManager.getWifiState() == WifiManager.WIFI_STATE_DISABLED) {
return;
}
// If Wi-Fi is trying to turn on, wait for that to complete before continuing.
if (mWifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLING) {
if (!Utils.waitUntil(
() -> mWifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLED,
TIMEOUT_TOGGLE_STATE)) {
Log.e(String.format("Wi-Fi failed to stabilize after %ss.", TIMEOUT_TOGGLE_STATE));
}
}
if (!mWifiManager.setWifiEnabled(false)) {
throw new WifiManagerSnippetException("Failed to initiate disabling Wi-Fi.");
}
if (!Utils.waitUntil(
() -> mWifiManager.getWifiState() == WifiManager.WIFI_STATE_DISABLED,
TIMEOUT_TOGGLE_STATE)) {
throw new WifiManagerSnippetException(
String.format(
"Failed to disable Wi-Fi after %ss, timeout!", TIMEOUT_TOGGLE_STATE));
}
}
@Rpc(description = "Checks if Wi-Fi is enabled.")
public boolean wifiIsEnabled() {
return mWifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLED;
}
@Rpc(description = "Trigger Wi-Fi scan.")
public void wifiStartScan() throws WifiManagerSnippetException {
if (!mWifiManager.startScan()) {
throw new WifiManagerSnippetException("Failed to initiate Wi-Fi scan.");
}
}
@Rpc(
description =
"Get Wi-Fi scan results, which is a list of serialized WifiScanResult objects.")
public JSONArray wifiGetCachedScanResults() throws JSONException {
JSONArray results = new JSONArray();
for (ScanResult result : mWifiManager.getScanResults()) {
results.put(mJsonSerializer.toJson(result));
}
return results;
}
@Rpc(
description =
"Start scan, wait for scan to complete, and return results, which is a list of "
+ "serialized WifiScanResult objects.")
public JSONArray wifiScanAndGetResults()
throws InterruptedException, JSONException, WifiManagerSnippetException {
mContext.registerReceiver(
new WifiScanReceiver(),
new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
wifiStartScan();
mIsScanResultAvailable = false;
if (!Utils.waitUntil(() -> mIsScanResultAvailable, 2 * 60)) {
throw new WifiManagerSnippetException(
"Failed to get scan results after 2min, timeout!");
}
return wifiGetCachedScanResults();
}
@Rpc(
description =
"Connects to a Wi-Fi network. This covers the common network types like open and "
+ "WPA2.")
public void wifiConnectSimple(String ssid, @Nullable String password)
throws InterruptedException, JSONException, WifiManagerSnippetException {
JSONObject config = new JSONObject();
config.put("SSID", ssid);
if (password != null) {
config.put("password", password);
}
wifiConnect(config);
}
/**
* Gets the {@link WifiConfiguration} of a Wi-Fi network that has already been configured.
*
* <p>If the network has not been configured, returns null.
*
* <p>A network is configured if a WifiConfiguration was created for it and added with {@link
* WifiManager#addNetwork(WifiConfiguration)}.
*/
private WifiConfiguration getExistingConfiguredNetwork(String ssid) {
List<WifiConfiguration> wifiConfigs = mWifiManager.getConfiguredNetworks();
if (wifiConfigs == null) {
return null;
}
for (WifiConfiguration config : wifiConfigs) {
if (config.SSID.equals(ssid)) {
return config;
}
}
return null;
}
/**
* Connect to a Wi-Fi network.
*
* @param wifiNetworkConfig A JSON object that contains the info required to connect to a Wi-Fi
* network. It follows the fields of WifiConfiguration type, e.g. {"SSID": "myWifi",
* "password": "12345678"}.
* @throws InterruptedException
* @throws JSONException
* @throws WifiManagerSnippetException
*/
@Rpc(description = "Connects to a Wi-Fi network.")
public void wifiConnect(JSONObject wifiNetworkConfig)
throws InterruptedException, JSONException, WifiManagerSnippetException {
Log.d("Got network config: " + wifiNetworkConfig);
WifiConfiguration wifiConfig = JsonDeserializer.jsonToWifiConfig(wifiNetworkConfig);
String SSID = wifiConfig.SSID;
// Return directly if network is already connected.
WifiInfo connectionInfo = mWifiManager.getConnectionInfo();
if (connectionInfo.getNetworkId() != -1
&& connectionInfo.getSSID().equals(wifiConfig.SSID)) {
Log.d("Network " + connectionInfo.getSSID() + " is already connected.");
return;
}
int networkId;
// If this is a network with a known SSID, connect with the existing config.
// We have to do this because in N+, network configs can only be modified by the UID that
// created the network. So any attempt to modify a network config that does not belong to us
// would result in error.
WifiConfiguration existingConfig = getExistingConfiguredNetwork(wifiConfig.SSID);
if (existingConfig != null) {
Log.w(
"Connecting to network \""
+ existingConfig.SSID
+ "\" with its existing configuration: "
+ existingConfig.toString());
wifiConfig = existingConfig;
networkId = wifiConfig.networkId;
} else {
// If this is a network with a new SSID, add the network.
networkId = mWifiManager.addNetwork(wifiConfig);
}
mWifiManager.disconnect();
if (!mWifiManager.enableNetwork(networkId, true)) {
throw new WifiManagerSnippetException(
"Failed to enable Wi-Fi network of ID: " + networkId);
}
if (!mWifiManager.reconnect()) {
throw new WifiManagerSnippetException(
"Failed to reconnect to Wi-Fi network of ID: " + networkId);
}
if (!Utils.waitUntil(
() ->
mWifiManager.getConnectionInfo().getSSID().equals(SSID)
&& mWifiManager.getConnectionInfo().getNetworkId() != -1 && mWifiManager
.getConnectionInfo().getSupplicantState().equals(SupplicantState.COMPLETED),
90)) {
throw new WifiManagerSnippetException(
String.format(
"Failed to connect to '%s', timeout! Current connection: '%s'",
wifiNetworkConfig, mWifiManager.getConnectionInfo().getSSID()));
}
Log.d(
"Connected to network '"
+ mWifiManager.getConnectionInfo().getSSID()
+ "' with ID "
+ mWifiManager.getConnectionInfo().getNetworkId());
}
@Rpc(
description =
"Forget a configured Wi-Fi network by its network ID, which is part of the"
+ " WifiConfiguration.")
public void wifiRemoveNetwork(Integer networkId) throws WifiManagerSnippetException {
if (!mWifiManager.removeNetwork(networkId)) {
throw new WifiManagerSnippetException("Failed to remove network of ID: " + networkId);
}
}
@Rpc(
description =
"Get the list of configured Wi-Fi networks, each is a serialized "
+ "WifiConfiguration object.")
public List<JSONObject> wifiGetConfiguredNetworks() throws JSONException {
List<JSONObject> networks = new ArrayList<>();
for (WifiConfiguration config : mWifiManager.getConfiguredNetworks()) {
networks.add(mJsonSerializer.toJson(config));
}
return networks;
}
@RpcMinSdk(Build.VERSION_CODES.LOLLIPOP)
@Rpc(description = "Enable or disable wifi verbose logging.")
public void wifiSetVerboseLogging(boolean enable) throws Throwable {
Utils.invokeByReflection(mWifiManager, "enableVerboseLogging", enable ? 1 : 0);
}
@Rpc(
description =
"Get the information about the active Wi-Fi connection, which is a serialized "
+ "WifiInfo object.")
public JSONObject wifiGetConnectionInfo() throws JSONException {
return mJsonSerializer.toJson(mWifiManager.getConnectionInfo());
}
@Rpc(
description =
"Get the info from last successful DHCP request, which is a serialized DhcpInfo "
+ "object.")
public JSONObject wifiGetDhcpInfo() throws JSONException {
return mJsonSerializer.toJson(mWifiManager.getDhcpInfo());
}
@Rpc(description = "Check whether Wi-Fi Soft AP (hotspot) is enabled.")
public boolean wifiIsApEnabled() throws Throwable {
return (boolean) Utils.invokeByReflection(mWifiManager, "isWifiApEnabled");
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
@RpcMinSdk(Build.VERSION_CODES.LOLLIPOP)
@Rpc(
description =
"Check whether this device supports 5 GHz band Wi-Fi. "
+ "Turn on Wi-Fi before calling.")
public boolean wifiIs5GHzBandSupported() {
return mWifiManager.is5GHzBandSupported();
}
/**
* Enable Wi-Fi Soft AP (hotspot).
*
* @param configuration The same format as the param wifiNetworkConfig param for wifiConnect.
* @throws Throwable
*/
@Rpc(description = "Enable Wi-Fi Soft AP (hotspot).")
public void wifiEnableSoftAp(@Nullable JSONObject configuration) throws Throwable {
// If no configuration is provided, the existing configuration would be used.
WifiConfiguration wifiConfiguration = null;
if (configuration != null) {
wifiConfiguration = JsonDeserializer.jsonToWifiConfig(configuration);
// Have to trim off the extra quotation marks since Soft AP logic interprets
// WifiConfiguration.SSID literally, unlike the WifiManager connection logic.
wifiConfiguration.SSID = JsonSerializer.trimQuotationMarks(wifiConfiguration.SSID);
}
if (!(boolean)
Utils.invokeByReflection(
mWifiManager, "setWifiApEnabled", wifiConfiguration, true)) {
throw new WifiManagerSnippetException("Failed to initiate turning on Wi-Fi Soft AP.");
}
if (!Utils.waitUntil(() -> wifiIsApEnabled() == true, 60)) {
throw new WifiManagerSnippetException(
"Timed out after 60s waiting for Wi-Fi Soft AP state to turn on with configuration: "
+ configuration);
}
}
/** Disables Wi-Fi Soft AP (hotspot). */
@Rpc(description = "Disable Wi-Fi Soft AP (hotspot).")
public void wifiDisableSoftAp() throws Throwable {
if (!(boolean)
Utils.invokeByReflection(
mWifiManager,
"setWifiApEnabled",
null /* No configuration needed for disabling */,
false)) {
throw new WifiManagerSnippetException("Failed to initiate turning off Wi-Fi Soft AP.");
}
if (!Utils.waitUntil(() -> wifiIsApEnabled() == false, 60)) {
throw new WifiManagerSnippetException(
"Timed out after 60s waiting for Wi-Fi Soft AP state to turn off.");
}
}
@Override
public void shutdown() {}
private class WifiScanReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context c, Intent intent) {
String action = intent.getAction();
if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
mIsScanResultAvailable = true;
}
}
}
}