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