/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.media.nearby import android.media.INearbyMediaDevicesProvider import android.media.INearbyMediaDevicesUpdateCallback import com.android.systemui.dagger.SysUISingleton import android.os.IBinder import com.android.systemui.statusbar.CommandQueue import javax.inject.Inject /** * A service that acts as a bridge between (1) external clients that have data on nearby devices * that are able to play media and (2) internal clients (like media Output Switcher) that need data * on these nearby devices. */ @SysUISingleton class NearbyMediaDevicesManager @Inject constructor( commandQueue: CommandQueue, private val logger: NearbyMediaDevicesLogger ) { private var providers: MutableList = mutableListOf() private var activeCallbacks: MutableList = mutableListOf() private val commandQueueCallbacks = object : CommandQueue.Callbacks { override fun registerNearbyMediaDevicesProvider(newProvider: INearbyMediaDevicesProvider) { if (providers.contains(newProvider)) { return } activeCallbacks.forEach { newProvider.registerNearbyDevicesCallback(it) } providers.add(newProvider) logger.logProviderRegistered(providers.size) newProvider.asBinder().linkToDeath(deathRecipient, /* flags= */ 0) } override fun unregisterNearbyMediaDevicesProvider( newProvider: INearbyMediaDevicesProvider ) { val isRemoved = providers.remove(newProvider) if (isRemoved) { logger.logProviderUnregistered(providers.size) } } } private val deathRecipient = object : IBinder.DeathRecipient { override fun binderDied() { // Should not be used as binderDied(IBinder who) is overridden. } override fun binderDied(who: IBinder) { binderDiedInternal(who) } } init { commandQueue.addCallback(commandQueueCallbacks) } /** * Registers [callback] to be notified each time a device's range changes or when a new device * comes within range. * * If a new provider is added, previously-registered callbacks will be registered with the * new provider. */ fun registerNearbyDevicesCallback(callback: INearbyMediaDevicesUpdateCallback) { providers.forEach { it.registerNearbyDevicesCallback(callback) } activeCallbacks.add(callback) } /** * Un-registers [callback]. See [registerNearbyDevicesCallback]. */ fun unregisterNearbyDevicesCallback(callback: INearbyMediaDevicesUpdateCallback) { activeCallbacks.remove(callback) providers.forEach { it.unregisterNearbyDevicesCallback(callback) } } private fun binderDiedInternal(who: IBinder) { synchronized(providers) { for (i in providers.size - 1 downTo 0) { if (providers[i].asBinder() == who) { providers.removeAt(i) logger.logProviderBinderDied(providers.size) break } } } } }