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