blob: 7ae2dc5c0941ab04cdce5197cdd65f42c408109a [file] [log] [blame]
/*
* Copyright (C) 2020 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
import android.content.Context
import android.media.MediaRouter2Manager
import android.media.session.MediaController
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.Dumpable
import com.android.systemui.dump.DumpManager
import java.io.FileDescriptor
import java.io.PrintWriter
import java.util.concurrent.Executor
import javax.inject.Inject
import javax.inject.Singleton
/**
* Provides information about the route (ie. device) where playback is occurring.
*/
@Singleton
class MediaDeviceManager @Inject constructor(
private val context: Context,
private val localMediaManagerFactory: LocalMediaManagerFactory,
private val mr2manager: MediaRouter2Manager,
@Main private val fgExecutor: Executor,
private val mediaDataManager: MediaDataManager,
private val dumpManager: DumpManager
) : MediaDataManager.Listener, Dumpable {
private val listeners: MutableSet<Listener> = mutableSetOf()
private val entries: MutableMap<String, Token> = mutableMapOf()
init {
mediaDataManager.addListener(this)
dumpManager.registerDumpable(javaClass.name, this)
}
/**
* Add a listener for changes to the media route (ie. device).
*/
fun addListener(listener: Listener) = listeners.add(listener)
/**
* Remove a listener that has been registered with addListener.
*/
fun removeListener(listener: Listener) = listeners.remove(listener)
override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
if (oldKey != null && oldKey != key) {
val oldEntry = entries.remove(oldKey)
oldEntry?.stop()
}
var entry = entries[key]
if (entry == null || entry?.token != data.token) {
entry?.stop()
val controller = data.token?.let {
MediaController(context, it)
}
entry = Token(key, controller, localMediaManagerFactory.create(data.packageName))
entries[key] = entry
entry.start()
}
}
override fun onMediaDataRemoved(key: String) {
val token = entries.remove(key)
token?.stop()
token?.let {
listeners.forEach {
it.onKeyRemoved(key)
}
}
}
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
with(pw) {
println("MediaDeviceManager state:")
entries.forEach {
key, entry ->
println(" key=$key")
entry.dump(fd, pw, args)
}
}
}
private fun processDevice(key: String, device: MediaDevice?) {
val enabled = device != null
val data = MediaDeviceData(enabled, device?.iconWithoutBackground, device?.name)
listeners.forEach {
it.onMediaDeviceChanged(key, data)
}
}
interface Listener {
/** Called when the route has changed for a given notification. */
fun onMediaDeviceChanged(key: String, data: MediaDeviceData?)
/** Called when the notification was removed. */
fun onKeyRemoved(key: String)
}
private inner class Token(
val key: String,
val controller: MediaController?,
val localMediaManager: LocalMediaManager
) : LocalMediaManager.DeviceCallback {
val token
get() = controller?.sessionToken
private var started = false
private var current: MediaDevice? = null
set(value) {
if (!started || value != field) {
field = value
processDevice(key, value)
}
}
fun start() {
localMediaManager.registerCallback(this)
localMediaManager.startScan()
updateCurrent()
started = true
}
fun stop() {
started = false
localMediaManager.stopScan()
localMediaManager.unregisterCallback(this)
}
fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
val route = controller?.let {
mr2manager.getRoutingSessionForMediaController(it)
}
with(pw) {
println(" current device is ${current?.name}")
val type = controller?.playbackInfo?.playbackType
println(" PlaybackType=$type (1 for local, 2 for remote)")
println(" route=$route")
}
}
override fun onDeviceListUpdate(devices: List<MediaDevice>?) = fgExecutor.execute {
updateCurrent()
}
override fun onSelectedDeviceStateChanged(device: MediaDevice, state: Int) {
fgExecutor.execute {
updateCurrent()
}
}
private fun updateCurrent() {
val device = localMediaManager.getCurrentConnectedDevice()
controller?.let {
val route = mr2manager.getRoutingSessionForMediaController(it)
// If we get a null route, then don't trust the device. Just set to null to disable the
// output switcher chip.
current = if (route != null) device else null
} ?: run {
current = device
}
}
}
}