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