• 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.companion.AssociationInfo
20 import android.companion.CompanionDeviceService
21 import android.content.Intent
22 import android.os.Handler
23 import android.util.Log
24 import java.util.Collections.synchronizedMap
25 import kotlin.time.Duration
26 import kotlin.time.Duration.Companion.seconds
27 
28 sealed class CompanionService<T : CompanionService<T>>(
29     private val instanceHolder: InstanceHolder<T>
30 ) : CompanionDeviceService() {
31     @Volatile var isBound: Boolean = false
32         private set(isBound) {
33             Log.d(TAG, "$this.isBound=$isBound")
34             if (!isBound && !connectedDevices.isEmpty())
35                 error("Unbinding while there are connected devices")
36             field = isBound
37         }
38 
39     val connectedDevices: Collection<AssociationInfo>
40         get() = _connectedDevices.values
41 
42     val associationIdsForConnectedDevices: Collection<Int>
43         get() = _connectedDevices.keys
44 
45     private val _connectedDevices: MutableMap<Int, AssociationInfo> =
46             synchronizedMap(mutableMapOf())
47 
onCreatenull48     override fun onCreate() {
49         Log.d(TAG, "$this.onCreate()")
50         super.onCreate()
51         instanceHolder.instance = this as T
52     }
53 
onBindCompanionDeviceServicenull54     override fun onBindCompanionDeviceService(intent: Intent) {
55         Log.d(TAG, "$this.onBindCompanionDeviceService()")
56         isBound = true
57     }
58 
onDeviceAppearednull59     override fun onDeviceAppeared(associationInfo: AssociationInfo) {
60         Log.d(TAG, "$this.onDevice_Appeared(), association=$associationInfo")
61         _connectedDevices[associationInfo.id] = associationInfo
62 
63         super.onDeviceAppeared(associationInfo)
64     }
65 
onDeviceDisappearednull66     override fun onDeviceDisappeared(associationInfo: AssociationInfo) {
67         Log.d(TAG, "$this.onDevice_Disappeared(), association=$associationInfo")
68         _connectedDevices.remove(associationInfo.id)
69                 ?: error("onDeviceAppeared() has not been called for association with id " +
70                         "${associationInfo.id}")
71 
72         super.onDeviceDisappeared(associationInfo)
73     }
74 
75     // For now, we need to "post" a Runnable that sets isBound to false to the Main Thread's
76     // Handler, because this may be called between invocations of
77     // CompanionDeviceService.Stub.onDeviceAppeared() and the "real"
78     // CompanionDeviceService.onDeviceAppeared(), which would cause an error() in isBound setter.
onUnbindnull79     override fun onUnbind(intent: Intent?) = super.onUnbind(intent)
80             .also {
81                 Log.d(TAG, "$this.onUnbind()")
82                 Handler.getMain().post { isBound = false }
83             }
84 
onDestroynull85     override fun onDestroy() {
86         Log.d(TAG, "$this.onDestroy()")
87         instanceHolder.instance = null
88         super.onDestroy()
89     }
90 
removeConnectedDevicenull91     fun removeConnectedDevice(associationId: Int) {
92         _connectedDevices.remove(associationId)
93     }
94 }
95 
96 sealed class InstanceHolder<T : CompanionService<T>> {
97     // Need synchronization, because the setter will be called from the Main thread, while the
98     // getter is expected to be called mostly from the instrumentation thread.
99     var instance: T? = null
100         @Synchronized internal set
101         @Synchronized get
102 
103     val isBound: Boolean
104         get() = instance?.isBound ?: false
105 
106     val connectedDevices: Collection<AssociationInfo>
107         get() = instance?.connectedDevices ?: emptySet()
108 
109     val associationIdsForConnectedDevices: Collection<Int>
110         get() = instance?.associationIdsForConnectedDevices ?: emptySet()
111 
waitForBindnull112     fun waitForBind(timeout: Duration = 1.seconds) {
113         if (!waitFor(timeout) { isBound })
114             throw AssertionError("Service hasn't been bound")
115     }
116 
waitForUnbindnull117     fun waitForUnbind(timeout: Duration) {
118         if (!waitFor(timeout) { !isBound })
119             throw AssertionError("Service hasn't been unbound")
120     }
121 
waitAssociationToAppearnull122     fun waitAssociationToAppear(associationId: Int, timeout: Duration = 1.seconds) {
123         val appeared = waitFor(timeout) {
124             associationIdsForConnectedDevices.contains(associationId)
125         }
126         if (!appeared) throw AssertionError("""Association with $associationId hasn't "appeared"""")
127     }
128 
129     fun waitAssociationToDisappear(associationId: Int, timeout: Duration = 1.seconds) {
130         val gone = waitFor(timeout) {
131             !associationIdsForConnectedDevices.contains(associationId)
132         }
133         if (!gone) throw AssertionError("""Association with $associationId hasn't "disappeared"""")
134     }
135 
136     // This is a useful function to use to conveniently "forget" that a device is currently present.
137     // Use to bypass the "unbinding while there are connected devices" for simulated devices.
138     // (Don't worry! they would have removed themselves after 1 minute anyways!)
139     fun forgetDevicePresence(associationId: Int) {
140         instance?.removeConnectedDevice(associationId)
141     }
142 }
143 
144 class PrimaryCompanionService : CompanionService<PrimaryCompanionService>(Companion) {
145     companion object : InstanceHolder<PrimaryCompanionService>()
146 }
147 
148 class SecondaryCompanionService : CompanionService<SecondaryCompanionService>(Companion) {
149     companion object : InstanceHolder<SecondaryCompanionService>()
150 }
151 
152 class MissingPermissionCompanionService
153     : CompanionService<MissingPermissionCompanionService>(Companion) {
154     companion object : InstanceHolder<MissingPermissionCompanionService>()
155 }
156 
157 class MissingIntentFilterActionCompanionService
158     : CompanionService<MissingIntentFilterActionCompanionService>(Companion) {
159     companion object : InstanceHolder<MissingIntentFilterActionCompanionService>()
160 }