1 /* 2 * Copyright (C) 2024 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 17 package com.google.snippet.connectivity 18 19 import android.Manifest.permission.NETWORK_SETTINGS 20 import android.Manifest.permission.OVERRIDE_WIFI_CONFIG 21 import android.content.pm.PackageManager.FEATURE_TELEPHONY 22 import android.content.pm.PackageManager.FEATURE_WIFI 23 import android.net.ConnectivityManager 24 import android.net.Network 25 import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED 26 import android.net.NetworkCapabilities.TRANSPORT_WIFI 27 import android.net.NetworkRequest 28 import android.net.cts.util.CtsNetUtils 29 import android.net.cts.util.CtsTetheringUtils 30 import android.net.wifi.ScanResult 31 import android.net.wifi.SoftApConfiguration 32 import android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA2_PSK 33 import android.net.wifi.WifiConfiguration 34 import android.net.wifi.WifiInfo 35 import android.net.wifi.WifiManager 36 import android.net.wifi.WifiNetworkSpecifier 37 import android.net.wifi.WifiSsid 38 import androidx.test.platform.app.InstrumentationRegistry 39 import com.android.compatibility.common.util.PropertyUtil 40 import com.android.modules.utils.build.SdkLevel 41 import com.android.testutils.AutoReleaseNetworkCallbackRule 42 import com.android.testutils.ConnectUtil 43 import com.android.testutils.NetworkCallbackHelper 44 import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged 45 import com.android.testutils.TestableNetworkCallback 46 import com.android.testutils.runAsShell 47 import com.google.android.mobly.snippet.Snippet 48 import com.google.android.mobly.snippet.rpc.Rpc 49 import org.junit.Rule 50 51 class ConnectivityMultiDevicesSnippet : Snippet { 52 @get:Rule 53 val networkCallbackRule = AutoReleaseNetworkCallbackRule() 54 private val context = InstrumentationRegistry.getInstrumentation().getTargetContext() 55 private val wifiManager = context.getSystemService(WifiManager::class.java)!! 56 private val cm = context.getSystemService(ConnectivityManager::class.java)!! 57 private val pm = context.packageManager 58 private val ctsNetUtils = CtsNetUtils(context) 59 private val cbHelper = NetworkCallbackHelper() 60 private val ctsTetheringUtils = CtsTetheringUtils(context) 61 private var oldSoftApConfig: SoftApConfiguration? = null 62 shutdownnull63 override fun shutdown() { 64 cbHelper.unregisterAll() 65 } 66 67 @Rpc(description = "Check whether the device has wifi feature.") hasWifiFeaturenull68 fun hasWifiFeature() = pm.hasSystemFeature(FEATURE_WIFI) 69 70 @Rpc(description = "Check whether the device has telephony feature.") 71 fun hasTelephonyFeature() = pm.hasSystemFeature(FEATURE_TELEPHONY) 72 73 @Rpc(description = "Check whether the device supporters AP + STA concurrency.") 74 fun isStaApConcurrencySupported() = wifiManager.isStaApConcurrencySupported() 75 76 @Rpc(description = "Check whether the device SDK is as least T") 77 fun isAtLeastT() = SdkLevel.isAtLeastT() 78 79 @Rpc(description = "Return whether the Sdk level is at least V.") 80 fun isAtLeastV() = SdkLevel.isAtLeastV() 81 82 @Rpc(description = "Return the API level that the VSR requirement must be fulfilled.") 83 fun getVsrApiLevel() = PropertyUtil.getVsrApiLevel() 84 85 @Rpc(description = "Request cellular connection and ensure it is the default network.") 86 fun requestCellularAndEnsureDefault() { 87 ctsNetUtils.disableWifi() 88 val network = cbHelper.requestCell() 89 ctsNetUtils.expectNetworkIsSystemDefault(network) 90 } 91 92 @Rpc(description = "Reconnect to wifi if supported.") reconnectWifiIfSupportednull93 fun reconnectWifiIfSupported() { 94 ctsNetUtils.reconnectWifiIfSupported() 95 } 96 97 @Rpc(description = "Unregister all connections.") unregisterAllnull98 fun unregisterAll() { 99 cbHelper.unregisterAll() 100 } 101 102 @Rpc(description = "Ensure any wifi is connected and is the default network.") ensureWifiIsDefaultnull103 fun ensureWifiIsDefault() { 104 val network = ctsNetUtils.ensureWifiConnected() 105 ctsNetUtils.expectNetworkIsSystemDefault(network) 106 } 107 108 @Rpc(description = "Connect to specified wifi network.") 109 // Suppress warning because WifiManager methods to connect to a config are 110 // documented not to be deprecated for privileged users. 111 @Suppress("DEPRECATION") connectToWifinull112 fun connectToWifi(ssid: String, passphrase: String): Long { 113 val specifier = WifiNetworkSpecifier.Builder() 114 .setBand(ScanResult.WIFI_BAND_24_GHZ) 115 .build() 116 val wifiConfig = WifiConfiguration() 117 wifiConfig.SSID = "\"" + ssid + "\"" 118 wifiConfig.preSharedKey = "\"" + passphrase + "\"" 119 wifiConfig.hiddenSSID = true 120 wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK) 121 wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP) 122 wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP) 123 124 // Add the test configuration and connect to it. 125 val connectUtil = ConnectUtil(context) 126 connectUtil.connectToWifiConfig(wifiConfig) 127 128 // Implement manual SSID matching. Specifying the SSID in 129 // NetworkSpecifier is ineffective 130 // (see WifiNetworkAgentSpecifier#canBeSatisfiedBy for details). 131 // Note that holding permission is necessary when waiting for 132 // the callbacks. The handler thread checks permission; if 133 // it's not present, the SSID will be redacted. 134 val networkCallback = TestableNetworkCallback() 135 val wifiRequest = NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build() 136 return runAsShell(NETWORK_SETTINGS) { 137 // Register the network callback is needed here. 138 // This is to avoid the race condition where callback is fired before 139 // acquiring permission. 140 networkCallbackRule.registerNetworkCallback(wifiRequest, networkCallback) 141 return@runAsShell networkCallback.eventuallyExpect<CapabilitiesChanged> { 142 // Remove double quotes. 143 val ssidFromCaps = (WifiInfo::sanitizeSsid)(it.caps.ssid) 144 ssidFromCaps == ssid && it.caps.hasCapability(NET_CAPABILITY_VALIDATED) 145 }.network.networkHandle 146 } 147 } 148 149 @Rpc(description = "Get interface name from NetworkHandle") getInterfaceNameFromNetworkHandlenull150 fun getInterfaceNameFromNetworkHandle(networkHandle: Long): String { 151 val network = Network.fromNetworkHandle(networkHandle) 152 return cm.getLinkProperties(network)!!.getInterfaceName()!! 153 } 154 155 @Rpc(description = "Check whether the device supports hotspot feature.") hasHotspotFeaturenull156 fun hasHotspotFeature(): Boolean { 157 val tetheringCallback = ctsTetheringUtils.registerTetheringEventCallback() 158 try { 159 return tetheringCallback.isWifiTetheringSupported(context) 160 } finally { 161 ctsTetheringUtils.unregisterTetheringEventCallback(tetheringCallback) 162 } 163 } 164 165 @Rpc(description = "Start a hotspot with given SSID and passphrase.") startHotspotnull166 fun startHotspot(ssid: String, passphrase: String): String { 167 // Store old config. 168 runAsShell(OVERRIDE_WIFI_CONFIG) { 169 oldSoftApConfig = wifiManager.getSoftApConfiguration() 170 } 171 172 val softApConfig = SoftApConfiguration.Builder() 173 .setWifiSsid(WifiSsid.fromBytes(ssid.toByteArray())) 174 .setPassphrase(passphrase, SECURITY_TYPE_WPA2_PSK) 175 .setBand(SoftApConfiguration.BAND_2GHZ) 176 .build() 177 runAsShell(OVERRIDE_WIFI_CONFIG) { 178 wifiManager.setSoftApConfiguration(softApConfig) 179 } 180 val tetheringCallback = ctsTetheringUtils.registerTetheringEventCallback() 181 try { 182 tetheringCallback.expectNoTetheringActive() 183 return ctsTetheringUtils.startWifiTethering(tetheringCallback).getInterface() 184 } finally { 185 ctsTetheringUtils.unregisterTetheringEventCallback(tetheringCallback) 186 } 187 } 188 189 @Rpc(description = "Stop all tethering.") stopAllTetheringnull190 fun stopAllTethering() { 191 ctsTetheringUtils.stopAllTethering() 192 193 // Restore old config. 194 oldSoftApConfig?.let { 195 runAsShell(OVERRIDE_WIFI_CONFIG) { 196 wifiManager.setSoftApConfiguration(it) 197 } 198 } 199 } 200 } 201