1 /*
2  * 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.controls.ui
18 
19 import com.android.systemui.dagger.SysUISingleton
20 import com.android.systemui.util.animation.MeasurementOutput
21 import com.android.systemui.util.traceSection
22 import javax.inject.Inject
23 
24 /**
25  * A class responsible for managing all media host states of the various host locations and
26  * coordinating the heights among different players. This class can be used to get the most up to
27  * date state for any location.
28  */
29 @SysUISingleton
30 class MediaHostStatesManager @Inject constructor() {
31 
32     private val callbacks: MutableSet<Callback> = mutableSetOf()
33     private val controllers: MutableSet<MediaViewController> = mutableSetOf()
34 
35     /**
36      * The overall sizes of the carousel. This is needed to make sure all players in the carousel
37      * have equal size.
38      */
39     val carouselSizes: MutableMap<Int, MeasurementOutput> = mutableMapOf()
40 
41     /** A map with all media states of all locations. */
42     val mediaHostStates: MutableMap<Int, MediaHostState> = mutableMapOf()
43 
44     /**
45      * Notify that a media state for a given location has changed. Should only be called from Media
46      * hosts themselves.
47      */
48     fun updateHostState(@MediaLocation location: Int, hostState: MediaHostState) =
49         traceSection("MediaHostStatesManager#updateHostState") {
50             val currentState = mediaHostStates.get(location)
51             if (!hostState.equals(currentState)) {
52                 val newState = hostState.copy()
53                 mediaHostStates.put(location, newState)
54                 updateCarouselDimensions(location, hostState)
55                 // First update all the controllers to ensure they get the chance to measure
56                 for (controller in controllers) {
57                     controller.stateCallback.onHostStateChanged(location, newState)
58                 }
59 
60                 // Then update all other callbacks which may depend on the controllers above
61                 for (callback in callbacks) {
62                     callback.onHostStateChanged(location, newState)
63                 }
64             }
65         }
66 
67     /**
68      * Get the dimensions of all players combined, which determines the overall height of the media
69      * carousel and the media hosts.
70      */
71     fun updateCarouselDimensions(
72         @MediaLocation location: Int,
73         hostState: MediaHostState
74     ): MeasurementOutput =
75         traceSection("MediaHostStatesManager#updateCarouselDimensions") {
76             val result = MeasurementOutput(0, 0)
77             for (controller in controllers) {
78                 val measurement = controller.getMeasurementsForState(hostState)
79                 measurement?.let {
80                     if (it.measuredHeight > result.measuredHeight) {
81                         result.measuredHeight = it.measuredHeight
82                     }
83                     if (it.measuredWidth > result.measuredWidth) {
84                         result.measuredWidth = it.measuredWidth
85                     }
86                 }
87             }
88             carouselSizes[location] = result
89             return result
90         }
91 
92     /** Add a callback to be called when a MediaState has updated */
93     fun addCallback(callback: Callback) {
94         callbacks.add(callback)
95     }
96 
97     /** Remove a callback that listens to media states */
98     fun removeCallback(callback: Callback) {
99         callbacks.remove(callback)
100     }
101 
102     /**
103      * Register a controller that listens to media states and is used to determine the size of the
104      * media carousel
105      */
106     fun addController(controller: MediaViewController) {
107         controllers.add(controller)
108     }
109 
110     /** Notify the manager about the removal of a controller. */
111     fun removeController(controller: MediaViewController) {
112         controllers.remove(controller)
113     }
114 
115     interface Callback {
116         /**
117          * Notify the callbacks that a media state for a host has changed, and that the
118          * corresponding view states should be updated and applied
119          */
120         fun onHostStateChanged(@MediaLocation location: Int, mediaHostState: MediaHostState)
121     }
122 }
123