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