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