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