1 /*
<lambda>null2  * Copyright (C) 2021 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.android.testutils
18 
19 import com.android.ddmlib.testrunner.TestResult
20 import com.android.tradefed.config.Option
21 import com.android.tradefed.invoker.TestInformation
22 import com.android.tradefed.result.CollectingTestListener
23 import com.android.tradefed.result.ddmlib.DefaultRemoteAndroidTestRunner
24 import com.android.tradefed.targetprep.BaseTargetPreparer
25 import com.android.tradefed.targetprep.TargetSetupError
26 import com.android.tradefed.targetprep.suite.SuiteApkInstaller
27 
28 private const val CONNECTIVITY_CHECKER_APK = "ConnectivityTestPreparer.apk"
29 private const val CONNECTIVITY_PKG_NAME = "com.android.testutils.connectivitypreparer"
30 private const val CONNECTIVITY_CHECK_CLASS = "$CONNECTIVITY_PKG_NAME.ConnectivityCheckTest"
31 
32 // As per the <instrumentation> defined in the checker manifest
33 private const val CONNECTIVITY_CHECK_RUNNER_NAME = "androidx.test.runner.AndroidJUnitRunner"
34 private const val IGNORE_WIFI_CHECK = "ignore-wifi-check"
35 private const val IGNORE_MOBILE_DATA_CHECK = "ignore-mobile-data-check"
36 
37 // The default updater package names, which might be updating packages while the CTS
38 // are running
39 private val UPDATER_PKGS = arrayOf("com.google.android.gms", "com.android.vending")
40 
41 /**
42  * A target preparer that sets up and verifies a device for connectivity tests.
43  *
44  * For quick and dirty local testing, the connectivity check can be disabled by running tests with
45  * "atest -- \
46  * --test-arg com.android.testutils.ConnectivityTestTargetPreparer:ignore-mobile-data-check:true". \
47  * --test-arg com.android.testutils.ConnectivityTestTargetPreparer:ignore-wifi-check:true".
48  */
49 open class ConnectivityTestTargetPreparer : BaseTargetPreparer() {
50     private val installer = SuiteApkInstaller()
51 
52     @Option(
53         name = IGNORE_WIFI_CHECK,
54             description = "Disables the check for wifi"
55     )
56     private var ignoreWifiCheck = false
57     @Option(
58         name = IGNORE_MOBILE_DATA_CHECK,
59             description = "Disables the check for mobile data"
60     )
61     private var ignoreMobileDataCheck = false
62 
63     // The default value is never used, but false is a reasonable default
64     private var originalTestChainEnabled = false
65     private val originalUpdaterPkgsStatus = HashMap<String, Boolean>()
66 
67     override fun setUp(testInfo: TestInformation) {
68         if (isDisabled) return
69         disableGmsUpdate(testInfo)
70         originalTestChainEnabled = getTestChainEnabled(testInfo)
71         originalUpdaterPkgsStatus.putAll(getUpdaterPkgsStatus(testInfo))
72         setUpdaterNetworkingEnabled(
73             testInfo,
74             enableChain = true,
75             enablePkgs = UPDATER_PKGS.associateWith { false }
76         )
77         runConnectivityCheckApk(testInfo)
78         refreshTime(testInfo)
79     }
80 
81     private fun runConnectivityCheckApk(testInfo: TestInformation) {
82         installer.setCleanApk(true)
83         installer.addTestFileName(CONNECTIVITY_CHECKER_APK)
84         installer.setShouldGrantPermission(true)
85         installer.setUp(testInfo)
86 
87         val testMethods = mutableListOf<String>()
88         if (!ignoreWifiCheck) {
89             testMethods.add("testCheckWifiSetup")
90         }
91         if (!ignoreMobileDataCheck) {
92             testMethods.add("testCheckTelephonySetup")
93         }
94 
95         testMethods.forEach {
96             runTestMethod(testInfo, it)
97         }
98     }
99 
100     private fun runTestMethod(testInfo: TestInformation, method: String) {
101         val runner = DefaultRemoteAndroidTestRunner(
102             CONNECTIVITY_PKG_NAME,
103             CONNECTIVITY_CHECK_RUNNER_NAME,
104             testInfo.device.iDevice
105         )
106         runner.runOptions = "--no-hidden-api-checks"
107         runner.setMethodName(CONNECTIVITY_CHECK_CLASS, method)
108 
109         val receiver = CollectingTestListener()
110         if (!testInfo.device.runInstrumentationTests(runner, receiver)) {
111             throw TargetSetupError(
112                 "Device state check failed to complete",
113                 testInfo.device.deviceDescriptor
114             )
115         }
116 
117         val runResult = receiver.currentRunResults
118         if (runResult.isRunFailure) {
119             throw TargetSetupError(
120                 "Failed to check device state before the test: " +
121                     runResult.runFailureMessage,
122                 testInfo.device.deviceDescriptor
123             )
124         }
125 
126         val errorMsg = runResult.testResults.mapNotNull { (testDescription, testResult) ->
127             if (TestResult.TestStatus.FAILURE != testResult.status) {
128                 null
129             } else {
130                 "$testDescription: ${testResult.stackTrace}"
131             }
132         }.joinToString("\n")
133         if (errorMsg.isBlank()) return
134 
135         throw TargetSetupError(
136             "Device setup checks failed. Check the test bench: \n$errorMsg",
137             testInfo.device.deviceDescriptor
138         )
139     }
140 
141     private fun disableGmsUpdate(testInfo: TestInformation) {
142         // This will be a no-op on devices without root (su) or not using gservices, but that's OK.
143         testInfo.exec(
144             "su 0 am broadcast " +
145                 "-a com.google.gservices.intent.action.GSERVICES_OVERRIDE " +
146                 "-e finsky.play_services_auto_update_enabled false"
147         )
148     }
149 
150     private fun clearGmsUpdateOverride(testInfo: TestInformation) {
151         testInfo.exec(
152             "su 0 am broadcast " +
153                 "-a com.google.gservices.intent.action.GSERVICES_OVERRIDE " +
154                 "--esn finsky.play_services_auto_update_enabled"
155         )
156     }
157 
158     private fun setUpdaterNetworkingEnabled(
159             testInfo: TestInformation,
160             enableChain: Boolean,
161             enablePkgs: Map<String, Boolean>
162     ) {
163         // Build.VERSION_CODES.S = 31 where this is not available, then do nothing.
164         if (testInfo.device.getApiLevel() < 31) return
165         testInfo.exec("cmd connectivity set-chain3-enabled $enableChain")
166         enablePkgs.forEach { (pkg, allow) ->
167             testInfo.exec("cmd connectivity set-package-networking-enabled $allow $pkg")
168         }
169     }
170 
171     private fun getTestChainEnabled(testInfo: TestInformation) =
172             testInfo.exec("cmd connectivity get-chain3-enabled").contains("chain:enabled")
173 
174     private fun getUpdaterPkgsStatus(testInfo: TestInformation) =
175         UPDATER_PKGS.associateWith { pkg ->
176             !testInfo.exec("cmd connectivity get-package-networking-enabled $pkg")
177                 .contains(":deny")
178         }
179 
180     private fun refreshTime(testInfo: TestInformation,) {
181         // Forces a synchronous time refresh using the network. Time is fetched synchronously but
182         // this does not guarantee that system time is updated when it returns.
183         // This avoids flakes where the system clock rolls back, for example when using test
184         // settings like test_url_expiration_time in NetworkMonitor.
185         testInfo.exec("cmd network_time_update_service force_refresh")
186     }
187 
188     override fun tearDown(testInfo: TestInformation, e: Throwable?) {
189         if (isTearDownDisabled) return
190         installer.tearDown(testInfo, e)
191         setUpdaterNetworkingEnabled(
192             testInfo,
193             enableChain = originalTestChainEnabled,
194             enablePkgs = originalUpdaterPkgsStatus
195         )
196         clearGmsUpdateOverride(testInfo)
197     }
198 }
199