1 /*
2  * Copyright (C) 2022 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.systemui.media.nearby
18 
19 import android.media.INearbyMediaDevicesProvider
20 import android.media.INearbyMediaDevicesUpdateCallback
21 import com.android.systemui.dagger.SysUISingleton
22 import android.os.IBinder
23 import com.android.systemui.statusbar.CommandQueue
24 import javax.inject.Inject
25 
26 /**
27  * A service that acts as a bridge between (1) external clients that have data on nearby devices
28  * that are able to play media and (2) internal clients (like media Output Switcher) that need data
29  * on these nearby devices.
30  */
31 @SysUISingleton
32 class NearbyMediaDevicesManager @Inject constructor(
33     commandQueue: CommandQueue,
34     private val logger: NearbyMediaDevicesLogger
35 ) {
36     private var providers: MutableList<INearbyMediaDevicesProvider> = mutableListOf()
37     private var activeCallbacks: MutableList<INearbyMediaDevicesUpdateCallback> = mutableListOf()
38 
39     private val commandQueueCallbacks = object : CommandQueue.Callbacks {
40         override fun registerNearbyMediaDevicesProvider(newProvider: INearbyMediaDevicesProvider) {
41             if (providers.contains(newProvider)) {
42                 return
43             }
44             activeCallbacks.forEach {
45                 newProvider.registerNearbyDevicesCallback(it)
46             }
47             providers.add(newProvider)
48             logger.logProviderRegistered(providers.size)
49             newProvider.asBinder().linkToDeath(deathRecipient, /* flags= */ 0)
50         }
51 
52         override fun unregisterNearbyMediaDevicesProvider(
53             newProvider: INearbyMediaDevicesProvider
54         ) {
55             val isRemoved = providers.remove(newProvider)
56             if (isRemoved) {
57                 logger.logProviderUnregistered(providers.size)
58             }
59         }
60     }
61 
62     private val deathRecipient = object : IBinder.DeathRecipient {
63         override fun binderDied() {
64             // Should not be used as binderDied(IBinder who) is overridden.
65         }
66 
67         override fun binderDied(who: IBinder) {
68             binderDiedInternal(who)
69         }
70     }
71 
72     init {
73         commandQueue.addCallback(commandQueueCallbacks)
74     }
75 
76     /**
77      * Registers [callback] to be notified each time a device's range changes or when a new device
78      * comes within range.
79      *
80      * If a new provider is added, previously-registered callbacks will be registered with the
81      * new provider.
82      */
83     fun registerNearbyDevicesCallback(callback: INearbyMediaDevicesUpdateCallback) {
84         providers.forEach {
85             it.registerNearbyDevicesCallback(callback)
86         }
87         activeCallbacks.add(callback)
88     }
89 
90     /**
91      * Un-registers [callback]. See [registerNearbyDevicesCallback].
92      */
93     fun unregisterNearbyDevicesCallback(callback: INearbyMediaDevicesUpdateCallback) {
94         activeCallbacks.remove(callback)
95         providers.forEach {
96             it.unregisterNearbyDevicesCallback(callback)
97         }
98     }
99 
100     private fun binderDiedInternal(who: IBinder) {
101         synchronized(providers) {
102             for (i in providers.size - 1 downTo 0) {
103                 if (providers[i].asBinder() == who) {
104                     providers.removeAt(i)
105                     logger.logProviderBinderDied(providers.size)
106                     break
107                 }
108             }
109         }
110     }
111 }
112