• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * 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 android.companion.cts.common
18 
19 import android.Manifest
20 import android.annotation.CallSuper
21 import android.app.Instrumentation
22 import android.app.UiAutomation
23 import android.companion.AssociationInfo
24 import android.companion.AssociationRequest
25 import android.companion.CompanionDeviceManager
26 import android.content.Context
27 import android.content.pm.PackageManager
28 import android.net.MacAddress
29 import android.os.SystemClock.sleep
30 import android.os.SystemClock.uptimeMillis
31 import android.util.Log
32 import androidx.test.platform.app.InstrumentationRegistry
33 import com.android.compatibility.common.util.SystemUtil
34 import org.junit.After
35 import org.junit.Assume.assumeTrue
36 import org.junit.AssumptionViolatedException
37 import org.junit.Before
38 import java.io.IOException
39 import kotlin.test.assertContains
40 import kotlin.test.assertContentEquals
41 import kotlin.test.assertEquals
42 import kotlin.test.assertFalse
43 import kotlin.test.assertIs
44 import kotlin.test.assertTrue
45 import kotlin.time.Duration
46 import kotlin.time.Duration.Companion.milliseconds
47 import kotlin.time.Duration.Companion.seconds
48 
49 /**
50  * A base class for CompanionDeviceManager [Tests][org.junit.Test] to extend.
51  */
52 abstract class TestBase {
53     protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
54     protected val uiAutomation: UiAutomation = instrumentation.uiAutomation
55 
56     protected val context: Context = instrumentation.context
57     protected val userId = context.userId
58     protected val targetPackageName = instrumentation.targetContext.packageName
59 
60     protected val targetApp = AppHelper(instrumentation, userId, targetPackageName)
61 
<lambda>null62     protected val pm: PackageManager by lazy { context.packageManager!! }
<lambda>null63     private val hasCompanionDeviceSetupFeature by lazy {
64         pm.hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)
65     }
66 
<lambda>null67     protected val cdm: CompanionDeviceManager by lazy {
68         context.getSystemService(CompanionDeviceManager::class.java)!!
69     }
70 
71     @Before
base_setUpnull72     fun base_setUp() {
73         assumeTrue(hasCompanionDeviceSetupFeature)
74 
75         // Remove all existing associations (for the user).
76         assertEmpty(withShellPermissionIdentity {
77             cdm.disassociateAll()
78             cdm.allAssociations
79         })
80 
81         // Make sure CompanionDeviceServices are not bound.
82         assertValidCompanionDeviceServicesUnbind()
83 
84         setUp()
85     }
86 
87     @After
base_tearDownnull88     fun base_tearDown() {
89         if (!hasCompanionDeviceSetupFeature) return
90 
91         tearDown()
92 
93         // Remove all existing associations (for the user).
94         withShellPermissionIdentity { cdm.disassociateAll() }
95     }
96 
97     @CallSuper
setUpnull98     protected open fun setUp() {}
99 
100     @CallSuper
tearDownnull101     protected open fun tearDown() {}
102 
withShellPermissionIdentitynull103     protected fun <T> withShellPermissionIdentity(
104         vararg permissions: String,
105         block: () -> T
106     ): T {
107         if (permissions.isNotEmpty()) {
108             uiAutomation.adoptShellPermissionIdentity(*permissions)
109         } else {
110             uiAutomation.adoptShellPermissionIdentity()
111         }
112 
113         try {
114             return block()
115         } finally {
116             uiAutomation.dropShellPermissionIdentity()
117         }
118     }
119 
createSelfManagedAssociationnull120     protected fun createSelfManagedAssociation(
121         displayName: String,
122         onAssociationCreatedAction: ((AssociationInfo) -> Unit)? = null
123     ): Int {
124         val callback = RecordingCallback(onAssociationCreatedAction = onAssociationCreatedAction)
125         val request: AssociationRequest = AssociationRequest.Builder()
126                 .setSelfManaged(true)
127                 .setDisplayName(displayName)
128                 .build()
129         callback.assertInvokedByActions {
130             withShellPermissionIdentity(Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) {
131                 cdm.associate(request, SIMPLE_EXECUTOR, callback)
132             }
133         }
134 
135         val callbackInvocation = callback.invocations.first()
136         assertIs<RecordingCallback.OnAssociationCreated>(callbackInvocation)
137         return callbackInvocation.associationInfo.id
138     }
139 
runShellCommandnull140     protected fun runShellCommand(cmd: String) = instrumentation.runShellCommand(cmd)
141 
142     private fun CompanionDeviceManager.disassociateAll() =
143             allAssociations.forEach { disassociate(it.id) }
144 }
145 
146 const val TAG = "CtsCompanionDeviceManagerTestCases"
147 
assumeThatnull148 fun <T> assumeThat(message: String, obj: T, assumption: (T) -> Boolean) {
149     if (!assumption(obj)) throw AssumptionViolatedException(message)
150 }
151 
<lambda>null152 fun <T> assertEmpty(list: Collection<T>) = assertTrue("Collection is not empty") { list.isEmpty() }
153 
assertAssociationsnull154 fun assertAssociations(
155     actual: List<AssociationInfo>,
156     expected: Set<Pair<String, MacAddress?>>
157 ) = assertEquals(actual = actual.map { it.packageName to it.deviceMacAddress }.toSet(),
158         expected = expected)
159 
160 /**
161  * Assert that CDM binds valid CompanionDeviceServices, both primary and secondary.
162  * Use when services are expected to switch its state to "bound".
163  */
assertValidCompanionDeviceServicesBindnull164 fun assertValidCompanionDeviceServicesBind() =
165         assertTrue("Both valid CompanionDeviceServices - Primary and Secondary - should bind") {
166             waitFor(timeout = 1.seconds, interval = 100.milliseconds) {
167                 PrimaryCompanionService.isBound && SecondaryCompanionService.isBound
168             }
169         }
170 
171 /**
172  * Assert both primary and secondary CompanionDeviceServices stay bound.
173  * Use when services are expected to be in "bound" state already.
174  */
assertValidCompanionDeviceServicesRemainBoundnull175 fun assertValidCompanionDeviceServicesRemainBound() =
176         assertFalse("Both valid CompanionDeviceServices should stay bound") {
177             waitFor(timeout = 3.seconds, interval = 100.milliseconds) {
178                 !PrimaryCompanionService.isBound || !SecondaryCompanionService.isBound
179             }
180         }
181 
182 /**
183  * Assert that CDM unbinds valid CompanionDeviceServices, both primary and secondary.
184  * Use when services are expected to switch its state to "unbound".
185  */
assertValidCompanionDeviceServicesUnbindnull186 fun assertValidCompanionDeviceServicesUnbind() =
187         assertTrue("CompanionDeviceServices should not bind") {
188             waitFor(timeout = 1.seconds, interval = 100.milliseconds) {
189                 !PrimaryCompanionService.isBound && !SecondaryCompanionService.isBound
190             }
191         }
192 
193 /**
194  * Assert that neither primary nor secondary CompanionDeviceService is bound.
195  * Use when services are expected to be in "unbound" state already.
196  */
assertValidCompanionDeviceServicesRemainUnboundnull197 fun assertValidCompanionDeviceServicesRemainUnbound() =
198         assertFalse("CompanionDeviceServices should not be bound") {
199             waitFor(timeout = 3.seconds, interval = 100.milliseconds) {
200                 PrimaryCompanionService.isBound || SecondaryCompanionService.isBound
201             }
202         }
203 
204 /**
205  * Assert that CDM did not bind invalid CompanionDeviceServices
206  * (i.e. missing permission or intent-filter).
207  */
assertInvalidCompanionDeviceServicesNotBoundnull208 fun assertInvalidCompanionDeviceServicesNotBound() =
209         assertFalse("CompanionDeviceServices that do not require " +
210                 "BIND_COMPANION_DEVICE_SERVICE permission or do not declare an intent-filter for " +
211                 "\"android.companion.CompanionDeviceService\" action should not be bound") {
212             MissingPermissionCompanionService.isBound ||
213                     MissingIntentFilterActionCompanionService.isBound
214     }
215 
216 /**
217  * Assert that device (dis)appearance detection callback is only triggered for the primary
218  * CompanionDeviceService and not on any of the non-primary or invalid CompanionDeviceServices.
219  */
assertOnlyPrimaryCompanionDeviceServiceNotifiednull220 fun assertOnlyPrimaryCompanionDeviceServiceNotified(associationId: Int, appeared: Boolean) {
221     val snapshotSecondary = HashSet(SecondaryCompanionService.connectedDevices)
222     val snapshotUnauthorized = HashSet(MissingPermissionCompanionService.connectedDevices)
223     val snapshotInvalid = HashSet(MissingIntentFilterActionCompanionService.connectedDevices)
224 
225     // Check that the primary CompanionDeviceService received onDevice(Dis)Appeared() callback
226     if (appeared) {
227         PrimaryCompanionService.waitAssociationToAppear(associationId)
228         assertContains(PrimaryCompanionService.associationIdsForConnectedDevices, associationId)
229     } else {
230         PrimaryCompanionService.waitAssociationToDisappear(associationId)
231         assertFalse(PrimaryCompanionService.associationIdsForConnectedDevices
232                 .contains(associationId))
233     }
234 
235     // ... while neither the non-primary nor incorrectly defined CompanionDeviceServices -
236     // have NOT. (Give it 1 more second.)
237     sleepFor(1.seconds)
238     assertContentEquals(snapshotSecondary, SecondaryCompanionService.connectedDevices)
239     assertContentEquals(snapshotUnauthorized, MissingPermissionCompanionService.connectedDevices)
240     assertContentEquals(snapshotInvalid, MissingIntentFilterActionCompanionService.connectedDevices)
241 }
242 
243 /**
244  * @return whether the condition was met before time ran out.
245  */
waitFornull246 fun waitFor(
247     timeout: Duration = 10.seconds,
248     interval: Duration = 1.seconds,
249     condition: () -> Boolean
250 ): Boolean {
251     val startTime = uptimeMillis()
252     while (!condition()) {
253         if (uptimeMillis() - startTime > timeout.inWholeMilliseconds) return false
254         sleep(interval.inWholeMilliseconds)
255     }
256     return true
257 }
258 
waitForResultnull259 fun <R> waitForResult(
260     timeout: Duration = 10.seconds,
261     interval: Duration = 1.seconds,
262     block: () -> R
263 ): R? {
264     val startTime = uptimeMillis()
265     while (true) {
266         val result: R = block()
267         if (result != null) return result
268         sleep(interval.inWholeMilliseconds)
269         if (uptimeMillis() - startTime > timeout.inWholeMilliseconds) return null
270     }
271 }
272 
runShellCommandnull273 fun Instrumentation.runShellCommand(cmd: String): String {
274     Log.i(TAG, "Running shell command: '$cmd'")
275     try {
276         val out = SystemUtil.runShellCommand(this, cmd)
277         Log.i(TAG, "Out:\n$out")
278         return out
279     } catch (e: IOException) {
280         Log.e(TAG, "Error running shell command: $cmd")
281         throw e
282     }
283 }
284 
setSystemPropnull285 fun Instrumentation.setSystemProp(name: String, value: String) =
286         runShellCommand("setprop $name $value")
287 
288 fun MacAddress.toUpperCaseString() = toString().toUpperCase()
289 
290 fun sleepFor(duration: Duration) = sleep(duration.inWholeMilliseconds)