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.Dumpable 21 import com.android.systemui.Flags.mediaControlsUmoInflationInBackground 22 import com.android.systemui.dagger.SysUISingleton 23 import com.android.systemui.dump.DumpManager 24 import com.android.systemui.media.controls.ui.view.MediaHostState 25 import com.android.systemui.util.animation.MeasurementOutput 26 import java.io.PrintWriter 27 import javax.inject.Inject 28 29 private val TAG = "MediaHostStatesManager" 30 31 /** 32 * A class responsible for managing all media host states of the various host locations and 33 * coordinating the heights among different players. This class can be used to get the most up to 34 * date state for any location. 35 */ 36 @SysUISingleton 37 class MediaHostStatesManager @Inject constructor(dumpManager: DumpManager) : Dumpable { 38 39 private val callbacks: MutableSet<Callback> = mutableSetOf() 40 private val controllers: MutableSet<MediaViewController> = mutableSetOf() 41 42 /** 43 * The overall sizes of the carousel. This is needed to make sure all players in the carousel 44 * have equal size. 45 */ 46 val carouselSizes: MutableMap<Int, MeasurementOutput> = mutableMapOf() 47 48 /** A map with all media states of all locations. */ 49 val mediaHostStates: MutableMap<Int, MediaHostState> = mutableMapOf() 50 51 init { 52 dumpManager.registerNormalDumpable(TAG, this) 53 } 54 55 /** 56 * Notify that a media state for a given location has changed. Should only be called from Media 57 * hosts themselves. 58 */ updateHostStatenull59 fun updateHostState(@MediaLocation location: Int, hostState: MediaHostState) = 60 traceSection("MediaHostStatesManager#updateHostState") { 61 val currentState = mediaHostStates.get(location) 62 if (!hostState.equals(currentState)) { 63 val newState = hostState.copy() 64 mediaHostStates.put(location, newState) 65 updateCarouselDimensions(location, hostState) 66 // First update all the controllers to ensure they get the chance to measure 67 for (controller in controllers) { 68 controller.stateCallback.onHostStateChanged(location, newState) 69 } 70 71 // Then update all other callbacks which may depend on the controllers above 72 for (callback in callbacks) { 73 callback.onHostStateChanged(location, newState) 74 } 75 } 76 } 77 78 /** 79 * Get the dimensions of all players combined, which determines the overall height of the media 80 * carousel and the media hosts. 81 */ updateCarouselDimensionsnull82 fun updateCarouselDimensions( 83 @MediaLocation location: Int, 84 hostState: MediaHostState, 85 ): MeasurementOutput = 86 traceSection("MediaHostStatesManager#updateCarouselDimensions") { 87 val result = MeasurementOutput(0, 0) 88 var changed = false 89 for (controller in controllers) { 90 val measurement = controller.getMeasurementsForState(hostState) 91 measurement?.let { 92 if (it.measuredHeight > result.measuredHeight) { 93 result.measuredHeight = it.measuredHeight 94 changed = true 95 } 96 if (it.measuredWidth > result.measuredWidth) { 97 result.measuredWidth = it.measuredWidth 98 changed = true 99 } 100 } 101 } 102 if (mediaControlsUmoInflationInBackground()) { 103 // Set carousel size if result measurements changed. This avoids setting carousel 104 // size when this method gets called before the addition of media view controllers 105 if (!carouselSizes.contains(location) || changed) { 106 carouselSizes[location] = result 107 } 108 } else { 109 carouselSizes[location] = result 110 } 111 return carouselSizes[location] ?: result 112 } 113 114 /** Add a callback to be called when a MediaState has updated */ addCallbacknull115 fun addCallback(callback: Callback) { 116 callbacks.add(callback) 117 } 118 119 /** Remove a callback that listens to media states */ removeCallbacknull120 fun removeCallback(callback: Callback) { 121 callbacks.remove(callback) 122 } 123 124 /** 125 * Register a controller that listens to media states and is used to determine the size of the 126 * media carousel 127 */ addControllernull128 fun addController(controller: MediaViewController) { 129 controllers.add(controller) 130 } 131 132 /** Notify the manager about the removal of a controller. */ removeControllernull133 fun removeController(controller: MediaViewController) { 134 controllers.remove(controller) 135 } 136 dumpnull137 override fun dump(pw: PrintWriter, args: Array<out String>) { 138 pw.apply { 139 println("Controllers: $controllers") 140 println("Callbacks: $callbacks") 141 for ((location, size) in carouselSizes) { 142 println("Size $location: ${size.measuredWidth} x ${size.measuredHeight}") 143 } 144 for ((location, state) in mediaHostStates) { 145 println("Host $location: visible ${state.visible}") 146 } 147 } 148 } 149 150 interface Callback { 151 /** 152 * Notify the callbacks that a media state for a host has changed, and that the 153 * corresponding view states should be updated and applied 154 */ onHostStateChangednull155 fun onHostStateChanged(@MediaLocation location: Int, mediaHostState: MediaHostState) 156 } 157 } 158