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.NetworkCapabilities.NET_CAPABILITY_VALIDATED 25 import android.net.NetworkCapabilities.TRANSPORT_WIFI 26 import android.net.NetworkRequest 27 import android.net.cts.util.CtsNetUtils 28 import android.net.cts.util.CtsTetheringUtils 29 import android.net.wifi.ScanResult 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.WifiNetworkSpecifier 36 import android.net.wifi.WifiSsid 37 import androidx.test.platform.app.InstrumentationRegistry 38 import com.android.testutils.AutoReleaseNetworkCallbackRule 39 import com.android.testutils.ConnectUtil 40 import com.android.testutils.NetworkCallbackHelper 41 import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged 42 import com.android.testutils.TestableNetworkCallback 43 import com.android.testutils.runAsShell 44 import com.google.android.mobly.snippet.Snippet 45 import com.google.android.mobly.snippet.rpc.Rpc 46 import org.junit.Rule 47 48 class ConnectivityMultiDevicesSnippet : Snippet { 49 @get:Rule 50 val networkCallbackRule = AutoReleaseNetworkCallbackRule() 51 private val context = InstrumentationRegistry.getInstrumentation().getTargetContext() 52 private val wifiManager = context.getSystemService(WifiManager::class.java)!! 53 private val cm = context.getSystemService(ConnectivityManager::class.java)!! 54 private val pm = context.packageManager 55 private val ctsNetUtils = CtsNetUtils(context) 56 private val cbHelper = NetworkCallbackHelper() 57 private val ctsTetheringUtils = CtsTetheringUtils(context) 58 private var oldSoftApConfig: SoftApConfiguration? = null 59 shutdownnull60 override fun shutdown() { 61 cbHelper.unregisterAll() 62 } 63 64 @Rpc(description = "Check whether the device has wifi feature.") hasWifiFeaturenull65 fun hasWifiFeature() = pm.hasSystemFeature(FEATURE_WIFI) 66 67 @Rpc(description = "Check whether the device has telephony feature.") 68 fun hasTelephonyFeature() = pm.hasSystemFeature(FEATURE_TELEPHONY) 69 70 @Rpc(description = "Check whether the device supporters AP + STA concurrency.") 71 fun isStaApConcurrencySupported() = wifiManager.isStaApConcurrencySupported() 72 73 @Rpc(description = "Request cellular connection and ensure it is the default network.") 74 fun requestCellularAndEnsureDefault() { 75 ctsNetUtils.disableWifi() 76 val network = cbHelper.requestCell() 77 ctsNetUtils.expectNetworkIsSystemDefault(network) 78 } 79 80 @Rpc(description = "Unregister all connections.") unregisterAllnull81 fun unregisterAll() { 82 cbHelper.unregisterAll() 83 } 84 85 @Rpc(description = "Ensure any wifi is connected and is the default network.") ensureWifiIsDefaultnull86 fun ensureWifiIsDefault() { 87 val network = ctsNetUtils.ensureWifiConnected() 88 ctsNetUtils.expectNetworkIsSystemDefault(network) 89 } 90 91 @Rpc(description = "Connect to specified wifi network.") 92 // Suppress warning because WifiManager methods to connect to a config are 93 // documented not to be deprecated for privileged users. 94 @Suppress("DEPRECATION") connectToWifinull95 fun connectToWifi(ssid: String, passphrase: String): Long { 96 val specifier = WifiNetworkSpecifier.Builder() 97 .setBand(ScanResult.WIFI_BAND_24_GHZ) 98 .build() 99 val wifiConfig = WifiConfiguration() 100 wifiConfig.SSID = "\"" + ssid + "\"" 101 wifiConfig.preSharedKey = "\"" + passphrase + "\"" 102 wifiConfig.hiddenSSID = true 103 wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK) 104 wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP) 105 wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP) 106 107 // Add the test configuration and connect to it. 108 val connectUtil = ConnectUtil(context) 109 connectUtil.connectToWifiConfig(wifiConfig) 110 111 // Implement manual SSID matching. Specifying the SSID in 112 // NetworkSpecifier is ineffective 113 // (see WifiNetworkAgentSpecifier#canBeSatisfiedBy for details). 114 // Note that holding permission is necessary when waiting for 115 // the callbacks. The handler thread checks permission; if 116 // it's not present, the SSID will be redacted. 117 val networkCallback = TestableNetworkCallback() 118 val wifiRequest = NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build() 119 return runAsShell(NETWORK_SETTINGS) { 120 // Register the network callback is needed here. 121 // This is to avoid the race condition where callback is fired before 122 // acquiring permission. 123 networkCallbackRule.registerNetworkCallback(wifiRequest, networkCallback) 124 return@runAsShell networkCallback.eventuallyExpect<CapabilitiesChanged> { 125 // Remove double quotes. 126 val ssidFromCaps = (WifiInfo::sanitizeSsid)(it.caps.ssid) 127 ssidFromCaps == ssid && it.caps.hasCapability(NET_CAPABILITY_VALIDATED) 128 }.network.networkHandle 129 } 130 } 131 132 @Rpc(description = "Check whether the device supports hotspot feature.") hasHotspotFeaturenull133 fun hasHotspotFeature(): Boolean { 134 val tetheringCallback = ctsTetheringUtils.registerTetheringEventCallback() 135 try { 136 return tetheringCallback.isWifiTetheringSupported(context) 137 } finally { 138 ctsTetheringUtils.unregisterTetheringEventCallback(tetheringCallback) 139 } 140 } 141 142 @Rpc(description = "Start a hotspot with given SSID and passphrase.") startHotspotnull143 fun startHotspot(ssid: String, passphrase: String) { 144 // Store old config. 145 runAsShell(OVERRIDE_WIFI_CONFIG) { 146 oldSoftApConfig = wifiManager.getSoftApConfiguration() 147 } 148 149 val softApConfig = SoftApConfiguration.Builder() 150 .setWifiSsid(WifiSsid.fromBytes(ssid.toByteArray())) 151 .setPassphrase(passphrase, SECURITY_TYPE_WPA2_PSK) 152 .setBand(SoftApConfiguration.BAND_2GHZ) 153 .build() 154 runAsShell(OVERRIDE_WIFI_CONFIG) { 155 wifiManager.setSoftApConfiguration(softApConfig) 156 } 157 val tetheringCallback = ctsTetheringUtils.registerTetheringEventCallback() 158 try { 159 tetheringCallback.expectNoTetheringActive() 160 ctsTetheringUtils.startWifiTethering(tetheringCallback) 161 } finally { 162 ctsTetheringUtils.unregisterTetheringEventCallback(tetheringCallback) 163 } 164 } 165 166 @Rpc(description = "Stop all tethering.") stopAllTetheringnull167 fun stopAllTethering() { 168 ctsTetheringUtils.stopAllTethering() 169 170 // Restore old config. 171 oldSoftApConfig?.let { 172 runAsShell(OVERRIDE_WIFI_CONFIG) { 173 wifiManager.setSoftApConfiguration(it) 174 } 175 } 176 } 177 } 178