/* * Copyright (C) 2024 The Android Open Source Project * * 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.snippet.connectivity import android.Manifest.permission.NETWORK_SETTINGS import android.Manifest.permission.OVERRIDE_WIFI_CONFIG import android.content.pm.PackageManager.FEATURE_TELEPHONY import android.content.pm.PackageManager.FEATURE_WIFI import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.NetworkRequest import android.net.cts.util.CtsNetUtils import android.net.cts.util.CtsTetheringUtils import android.net.wifi.ScanResult import android.net.wifi.SoftApConfiguration import android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA2_PSK import android.net.wifi.WifiConfiguration import android.net.wifi.WifiInfo import android.net.wifi.WifiManager import android.net.wifi.WifiNetworkSpecifier import android.net.wifi.WifiSsid import androidx.test.platform.app.InstrumentationRegistry import com.android.compatibility.common.util.PropertyUtil import com.android.modules.utils.build.SdkLevel import com.android.testutils.AutoReleaseNetworkCallbackRule import com.android.testutils.ConnectUtil import com.android.testutils.NetworkCallbackHelper import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged import com.android.testutils.TestableNetworkCallback import com.android.testutils.runAsShell import com.google.android.mobly.snippet.Snippet import com.google.android.mobly.snippet.rpc.Rpc import org.junit.Rule class ConnectivityMultiDevicesSnippet : Snippet { @get:Rule val networkCallbackRule = AutoReleaseNetworkCallbackRule() private val context = InstrumentationRegistry.getInstrumentation().getTargetContext() private val wifiManager = context.getSystemService(WifiManager::class.java)!! private val cm = context.getSystemService(ConnectivityManager::class.java)!! private val pm = context.packageManager private val ctsNetUtils = CtsNetUtils(context) private val cbHelper = NetworkCallbackHelper() private val ctsTetheringUtils = CtsTetheringUtils(context) private var oldSoftApConfig: SoftApConfiguration? = null override fun shutdown() { cbHelper.unregisterAll() } @Rpc(description = "Check whether the device has wifi feature.") fun hasWifiFeature() = pm.hasSystemFeature(FEATURE_WIFI) @Rpc(description = "Check whether the device has telephony feature.") fun hasTelephonyFeature() = pm.hasSystemFeature(FEATURE_TELEPHONY) @Rpc(description = "Check whether the device supporters AP + STA concurrency.") fun isStaApConcurrencySupported() = wifiManager.isStaApConcurrencySupported() @Rpc(description = "Check whether the device SDK is as least T") fun isAtLeastT() = SdkLevel.isAtLeastT() @Rpc(description = "Return whether the Sdk level is at least V.") fun isAtLeastV() = SdkLevel.isAtLeastV() @Rpc(description = "Return the API level that the VSR requirement must be fulfilled.") fun getVsrApiLevel() = PropertyUtil.getVsrApiLevel() @Rpc(description = "Request cellular connection and ensure it is the default network.") fun requestCellularAndEnsureDefault() { ctsNetUtils.disableWifi() val network = cbHelper.requestCell() ctsNetUtils.expectNetworkIsSystemDefault(network) } @Rpc(description = "Unregister all connections.") fun unregisterAll() { cbHelper.unregisterAll() } @Rpc(description = "Ensure any wifi is connected and is the default network.") fun ensureWifiIsDefault() { val network = ctsNetUtils.ensureWifiConnected() ctsNetUtils.expectNetworkIsSystemDefault(network) } @Rpc(description = "Connect to specified wifi network.") // Suppress warning because WifiManager methods to connect to a config are // documented not to be deprecated for privileged users. @Suppress("DEPRECATION") fun connectToWifi(ssid: String, passphrase: String): Long { val specifier = WifiNetworkSpecifier.Builder() .setBand(ScanResult.WIFI_BAND_24_GHZ) .build() val wifiConfig = WifiConfiguration() wifiConfig.SSID = "\"" + ssid + "\"" wifiConfig.preSharedKey = "\"" + passphrase + "\"" wifiConfig.hiddenSSID = true wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK) wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP) wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP) // Add the test configuration and connect to it. val connectUtil = ConnectUtil(context) connectUtil.connectToWifiConfig(wifiConfig) // Implement manual SSID matching. Specifying the SSID in // NetworkSpecifier is ineffective // (see WifiNetworkAgentSpecifier#canBeSatisfiedBy for details). // Note that holding permission is necessary when waiting for // the callbacks. The handler thread checks permission; if // it's not present, the SSID will be redacted. val networkCallback = TestableNetworkCallback() val wifiRequest = NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build() return runAsShell(NETWORK_SETTINGS) { // Register the network callback is needed here. // This is to avoid the race condition where callback is fired before // acquiring permission. networkCallbackRule.registerNetworkCallback(wifiRequest, networkCallback) return@runAsShell networkCallback.eventuallyExpect { // Remove double quotes. val ssidFromCaps = (WifiInfo::sanitizeSsid)(it.caps.ssid) ssidFromCaps == ssid && it.caps.hasCapability(NET_CAPABILITY_VALIDATED) }.network.networkHandle } } @Rpc(description = "Get interface name from NetworkHandle") fun getInterfaceNameFromNetworkHandle(networkHandle: Long): String { val network = Network.fromNetworkHandle(networkHandle) return cm.getLinkProperties(network)!!.getInterfaceName()!! } @Rpc(description = "Check whether the device supports hotspot feature.") fun hasHotspotFeature(): Boolean { val tetheringCallback = ctsTetheringUtils.registerTetheringEventCallback() try { return tetheringCallback.isWifiTetheringSupported(context) } finally { ctsTetheringUtils.unregisterTetheringEventCallback(tetheringCallback) } } @Rpc(description = "Start a hotspot with given SSID and passphrase.") fun startHotspot(ssid: String, passphrase: String): String { // Store old config. runAsShell(OVERRIDE_WIFI_CONFIG) { oldSoftApConfig = wifiManager.getSoftApConfiguration() } val softApConfig = SoftApConfiguration.Builder() .setWifiSsid(WifiSsid.fromBytes(ssid.toByteArray())) .setPassphrase(passphrase, SECURITY_TYPE_WPA2_PSK) .setBand(SoftApConfiguration.BAND_2GHZ) .build() runAsShell(OVERRIDE_WIFI_CONFIG) { wifiManager.setSoftApConfiguration(softApConfig) } val tetheringCallback = ctsTetheringUtils.registerTetheringEventCallback() try { tetheringCallback.expectNoTetheringActive() return ctsTetheringUtils.startWifiTethering(tetheringCallback).getInterface() } finally { ctsTetheringUtils.unregisterTetheringEventCallback(tetheringCallback) } } @Rpc(description = "Stop all tethering.") fun stopAllTethering() { ctsTetheringUtils.stopAllTethering() // Restore old config. oldSoftApConfig?.let { runAsShell(OVERRIDE_WIFI_CONFIG) { wifiManager.setSoftApConfiguration(it) } } } }