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