1 /* <lambda>null2 * Copyright (C) 2020 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 18 19 import android.content.Context 20 import android.media.MediaRouter2Manager 21 import android.media.session.MediaController 22 import com.android.settingslib.media.LocalMediaManager 23 import com.android.settingslib.media.MediaDevice 24 import com.android.systemui.dagger.qualifiers.Main 25 import com.android.systemui.Dumpable 26 import com.android.systemui.dump.DumpManager 27 import java.io.FileDescriptor 28 import java.io.PrintWriter 29 import java.util.concurrent.Executor 30 import javax.inject.Inject 31 import javax.inject.Singleton 32 33 /** 34 * Provides information about the route (ie. device) where playback is occurring. 35 */ 36 @Singleton 37 class MediaDeviceManager @Inject constructor( 38 private val context: Context, 39 private val localMediaManagerFactory: LocalMediaManagerFactory, 40 private val mr2manager: MediaRouter2Manager, 41 @Main private val fgExecutor: Executor, 42 private val mediaDataManager: MediaDataManager, 43 private val dumpManager: DumpManager 44 ) : MediaDataManager.Listener, Dumpable { 45 private val listeners: MutableSet<Listener> = mutableSetOf() 46 private val entries: MutableMap<String, Token> = mutableMapOf() 47 48 init { 49 mediaDataManager.addListener(this) 50 dumpManager.registerDumpable(javaClass.name, this) 51 } 52 53 /** 54 * Add a listener for changes to the media route (ie. device). 55 */ 56 fun addListener(listener: Listener) = listeners.add(listener) 57 58 /** 59 * Remove a listener that has been registered with addListener. 60 */ 61 fun removeListener(listener: Listener) = listeners.remove(listener) 62 63 override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { 64 if (oldKey != null && oldKey != key) { 65 val oldEntry = entries.remove(oldKey) 66 oldEntry?.stop() 67 } 68 var entry = entries[key] 69 if (entry == null || entry?.token != data.token) { 70 entry?.stop() 71 val controller = data.token?.let { 72 MediaController(context, it) 73 } 74 entry = Token(key, controller, localMediaManagerFactory.create(data.packageName)) 75 entries[key] = entry 76 entry.start() 77 } 78 } 79 80 override fun onMediaDataRemoved(key: String) { 81 val token = entries.remove(key) 82 token?.stop() 83 token?.let { 84 listeners.forEach { 85 it.onKeyRemoved(key) 86 } 87 } 88 } 89 90 override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) { 91 with(pw) { 92 println("MediaDeviceManager state:") 93 entries.forEach { 94 key, entry -> 95 println(" key=$key") 96 entry.dump(fd, pw, args) 97 } 98 } 99 } 100 101 private fun processDevice(key: String, device: MediaDevice?) { 102 val enabled = device != null 103 val data = MediaDeviceData(enabled, device?.iconWithoutBackground, device?.name) 104 listeners.forEach { 105 it.onMediaDeviceChanged(key, data) 106 } 107 } 108 109 interface Listener { 110 /** Called when the route has changed for a given notification. */ 111 fun onMediaDeviceChanged(key: String, data: MediaDeviceData?) 112 /** Called when the notification was removed. */ 113 fun onKeyRemoved(key: String) 114 } 115 116 private inner class Token( 117 val key: String, 118 val controller: MediaController?, 119 val localMediaManager: LocalMediaManager 120 ) : LocalMediaManager.DeviceCallback { 121 val token 122 get() = controller?.sessionToken 123 private var started = false 124 private var current: MediaDevice? = null 125 set(value) { 126 if (!started || value != field) { 127 field = value 128 processDevice(key, value) 129 } 130 } 131 fun start() { 132 localMediaManager.registerCallback(this) 133 localMediaManager.startScan() 134 updateCurrent() 135 started = true 136 } 137 fun stop() { 138 started = false 139 localMediaManager.stopScan() 140 localMediaManager.unregisterCallback(this) 141 } 142 fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) { 143 val route = controller?.let { 144 mr2manager.getRoutingSessionForMediaController(it) 145 } 146 with(pw) { 147 println(" current device is ${current?.name}") 148 val type = controller?.playbackInfo?.playbackType 149 println(" PlaybackType=$type (1 for local, 2 for remote)") 150 println(" route=$route") 151 } 152 } 153 override fun onDeviceListUpdate(devices: List<MediaDevice>?) = fgExecutor.execute { 154 updateCurrent() 155 } 156 override fun onSelectedDeviceStateChanged(device: MediaDevice, state: Int) { 157 fgExecutor.execute { 158 updateCurrent() 159 } 160 } 161 private fun updateCurrent() { 162 val device = localMediaManager.getCurrentConnectedDevice() 163 controller?.let { 164 val route = mr2manager.getRoutingSessionForMediaController(it) 165 // If we get a null route, then don't trust the device. Just set to null to disable the 166 // output switcher chip. 167 current = if (route != null) device else null 168 } ?: run { 169 current = device 170 } 171 } 172 } 173 } 174