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.SoftApConfiguration 31 import android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA2_PSK 32 import android.net.wifi.WifiConfiguration 33 import android.net.wifi.WifiInfo 34 import android.net.wifi.WifiManager 35 import android.net.wifi.WifiSsid 36 import android.os.Build.VERSION.CODENAME 37 import android.os.Build.VERSION.SDK_INT 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 isAtLeastPreReleaseCodenamenull67 private fun isAtLeastPreReleaseCodename(codeName: String): Boolean { 68 // Special case "REL", which means the build is not a pre-release build. 69 if ("REL".equals(CODENAME)) { 70 return false 71 } 72 73 // Otherwise lexically compare them. Return true if the build codename is equal to or 74 // greater than the requested codename. 75 return CODENAME.compareTo(codeName) >= 0 76 } 77 78 @Rpc(description = "Check whether the device has wifi feature.") hasWifiFeaturenull79 fun hasWifiFeature() = pm.hasSystemFeature(FEATURE_WIFI) 80 81 @Rpc(description = "Check whether the device has telephony feature.") 82 fun hasTelephonyFeature() = pm.hasSystemFeature(FEATURE_TELEPHONY) 83 84 @Rpc(description = "Check whether the device supporters AP + STA concurrency.") 85 fun isStaApConcurrencySupported() = wifiManager.isStaApConcurrencySupported() 86 87 @Rpc(description = "Check whether the device SDK is as least T") 88 fun isAtLeastT() = SdkLevel.isAtLeastT() 89 90 @Rpc(description = "Return whether the Sdk level is at least V.") 91 fun isAtLeastV() = SdkLevel.isAtLeastV() 92 93 @Rpc(description = "Check whether the device is at least B.") 94 fun isAtLeastB(): Boolean { 95 return SDK_INT >= 36 || (SDK_INT == 35 && isAtLeastPreReleaseCodename("Baklava")) 96 } 97 98 @Rpc(description = "Return the API level that the VSR requirement must be fulfilled.") getVsrApiLevelnull99 fun getVsrApiLevel() = PropertyUtil.getVsrApiLevel() 100 101 @Rpc(description = "Request cellular connection and ensure it is the default network.") 102 fun requestCellularAndEnsureDefault() { 103 ctsNetUtils.disableWifi() 104 val network = cbHelper.requestCell() 105 ctsNetUtils.expectNetworkIsSystemDefault(network) 106 } 107 108 @Rpc(description = "Reconnect to wifi if supported.") reconnectWifiIfSupportednull109 fun reconnectWifiIfSupported() { 110 ctsNetUtils.reconnectWifiIfSupported() 111 } 112 113 @Rpc(description = "Unregister all connections.") unregisterAllnull114 fun unregisterAll() { 115 cbHelper.unregisterAll() 116 } 117 118 @Rpc(description = "Ensure any wifi is connected and is the default network.") ensureWifiIsDefaultnull119 fun ensureWifiIsDefault() { 120 val network = ctsNetUtils.ensureWifiConnected() 121 ctsNetUtils.expectNetworkIsSystemDefault(network) 122 } 123 124 @Rpc(description = "Connect to specified wifi network.") 125 // Suppress warning because WifiManager methods to connect to a config are 126 // documented not to be deprecated for privileged users. 127 @Suppress("DEPRECATION") connectToWifinull128 fun connectToWifi(ssid: String, passphrase: String, requireValidation: Boolean): Long { 129 val wifiConfig = WifiConfiguration() 130 wifiConfig.SSID = "\"" + ssid + "\"" 131 wifiConfig.preSharedKey = "\"" + passphrase + "\"" 132 wifiConfig.hiddenSSID = true 133 wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK) 134 wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP) 135 wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP) 136 137 // Add the test configuration and connect to it. 138 val connectUtil = ConnectUtil(context) 139 connectUtil.connectToWifiConfig(wifiConfig) 140 141 // Implement manual SSID matching. Specifying the SSID in 142 // NetworkSpecifier is ineffective 143 // (see WifiNetworkAgentSpecifier#canBeSatisfiedBy for details). 144 // Note that holding permission is necessary when waiting for 145 // the callbacks. The handler thread checks permission; if 146 // it's not present, the SSID will be redacted. 147 val networkCallback = TestableNetworkCallback() 148 val wifiRequest = NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build() 149 return runAsShell(NETWORK_SETTINGS) { 150 // Register the network callback is needed here. 151 // This is to avoid the race condition where callback is fired before 152 // acquiring permission. 153 networkCallbackRule.registerNetworkCallback(wifiRequest, networkCallback) 154 return@runAsShell networkCallback.eventuallyExpect<CapabilitiesChanged> { 155 // Remove double quotes. 156 val ssidFromCaps = (WifiInfo::sanitizeSsid)(it.caps.ssid) 157 ssidFromCaps == ssid && (!requireValidation || 158 it.caps.hasCapability(NET_CAPABILITY_VALIDATED)) 159 }.network.networkHandle 160 } 161 } 162 163 @Rpc(description = "Get interface name from NetworkHandle") getInterfaceNameFromNetworkHandlenull164 fun getInterfaceNameFromNetworkHandle(networkHandle: Long): String { 165 val network = Network.fromNetworkHandle(networkHandle) 166 return cm.getLinkProperties(network)!!.getInterfaceName()!! 167 } 168 169 @Rpc(description = "Check whether the device supports hotspot feature.") hasHotspotFeaturenull170 fun hasHotspotFeature(): Boolean { 171 val tetheringCallback = ctsTetheringUtils.registerTetheringEventCallback() 172 try { 173 return tetheringCallback.isWifiTetheringSupported(context) 174 } finally { 175 ctsTetheringUtils.unregisterTetheringEventCallback(tetheringCallback) 176 } 177 } 178 179 @Rpc(description = "Start a hotspot with given SSID and passphrase.") startHotspotnull180 fun startHotspot(ssid: String, passphrase: String): String { 181 // Store old config. 182 runAsShell(OVERRIDE_WIFI_CONFIG) { 183 oldSoftApConfig = wifiManager.getSoftApConfiguration() 184 } 185 186 val softApConfig = SoftApConfiguration.Builder() 187 .setWifiSsid(WifiSsid.fromBytes(ssid.toByteArray())) 188 .setPassphrase(passphrase, SECURITY_TYPE_WPA2_PSK) 189 .setBand(SoftApConfiguration.BAND_2GHZ) 190 .build() 191 runAsShell(OVERRIDE_WIFI_CONFIG) { 192 wifiManager.setSoftApConfiguration(softApConfig) 193 } 194 val tetheringCallback = ctsTetheringUtils.registerTetheringEventCallback() 195 try { 196 tetheringCallback.expectNoTetheringActive() 197 return ctsTetheringUtils.startWifiTethering(tetheringCallback).getInterface() 198 } finally { 199 ctsTetheringUtils.unregisterTetheringEventCallback(tetheringCallback) 200 } 201 } 202 203 @Rpc(description = "Stop all tethering.") stopAllTetheringnull204 fun stopAllTethering() { 205 ctsTetheringUtils.stopAllTethering() 206 207 // Restore old config. 208 oldSoftApConfig?.let { 209 runAsShell(OVERRIDE_WIFI_CONFIG) { 210 wifiManager.setSoftApConfiguration(it) 211 } 212 } 213 } 214 } 215