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 }