blob: bccc3abd8a27e70a0bb62b112cb9dae316112592 [file] [log] [blame]
Selim Cinek9e517f72020-04-28 12:24:34 -07001package com.android.systemui.media
2
3import android.content.Context
Robert Snoeberger52dfb622020-05-21 16:31:29 -04004import android.graphics.Color
Selim Cinek9e517f72020-04-28 12:24:34 -07005import android.view.LayoutInflater
Robert Snoeberger52dfb622020-05-21 16:31:29 -04006import android.view.GestureDetector
7import android.view.MotionEvent
Selim Cinekf418bb02020-05-04 17:16:58 -07008import android.view.View
Selim Cinek9e517f72020-04-28 12:24:34 -07009import android.view.ViewGroup
Selim Cinekf418bb02020-05-04 17:16:58 -070010import android.widget.HorizontalScrollView
Selim Cinek9e517f72020-04-28 12:24:34 -070011import android.widget.LinearLayout
Robert Snoeberger52dfb622020-05-21 16:31:29 -040012import androidx.core.view.GestureDetectorCompat
Selim Cinek9e517f72020-04-28 12:24:34 -070013import com.android.systemui.R
Robert Snoeberger52dfb622020-05-21 16:31:29 -040014import com.android.systemui.qs.PageIndicator
Selim Cinek9e517f72020-04-28 12:24:34 -070015import com.android.systemui.statusbar.notification.VisualStabilityManager
Robert Snoebergerd4efdae2020-06-12 15:42:25 -040016import com.android.systemui.statusbar.policy.ConfigurationController
Selim Cinek54809622020-04-30 19:04:44 -070017import com.android.systemui.util.animation.UniqueObjectHostView
Selim Cinek2de5ebb2020-05-20 15:39:03 -070018import com.android.systemui.util.animation.requiresRemeasuring
Selim Cinek9e517f72020-04-28 12:24:34 -070019import javax.inject.Inject
Robert Snoeberger7cec5422020-05-29 17:09:14 -040020import javax.inject.Provider
Selim Cinek9e517f72020-04-28 12:24:34 -070021import javax.inject.Singleton
22
Robert Snoeberger52dfb622020-05-21 16:31:29 -040023private const val FLING_SLOP = 1000000
24
Selim Cinek9e517f72020-04-28 12:24:34 -070025/**
26 * Class that is responsible for keeping the view carousel up to date.
27 * This also handles changes in state and applies them to the media carousel like the expansion.
28 */
29@Singleton
30class MediaViewManager @Inject constructor(
31 private val context: Context,
Robert Snoeberger7cec5422020-05-29 17:09:14 -040032 private val mediaControlPanelFactory: Provider<MediaControlPanel>,
Selim Cinek9e517f72020-04-28 12:24:34 -070033 private val visualStabilityManager: VisualStabilityManager,
Selim Cinek2de5ebb2020-05-20 15:39:03 -070034 private val mediaHostStatesManager: MediaHostStatesManager,
Robert Snoebergerd4efdae2020-06-12 15:42:25 -040035 mediaManager: MediaDataCombineLatest,
36 configurationController: ConfigurationController
Selim Cinek9e517f72020-04-28 12:24:34 -070037) {
Selim Cinek2de5ebb2020-05-20 15:39:03 -070038
39 /**
40 * The desired location where we'll be at the end of the transformation. Usually this matches
41 * the end location, except when we're still waiting on a state update call.
42 */
43 @MediaLocation
44 private var desiredLocation: Int = -1
45
46 /**
47 * The ending location of the view where it ends when all animations and transitions have
48 * finished
49 */
50 @MediaLocation
51 private var currentEndLocation: Int = -1
52
53 /**
54 * The ending location of the view where it ends when all animations and transitions have
55 * finished
56 */
57 @MediaLocation
58 private var currentStartLocation: Int = -1
59
60 /**
61 * The progress of the transition or 1.0 if there is no transition happening
62 */
63 private var currentTransitionProgress: Float = 1.0f
64
65 /**
66 * The measured width of the carousel
67 */
68 private var carouselMeasureWidth: Int = 0
69
70 /**
71 * The measured height of the carousel
72 */
73 private var carouselMeasureHeight: Int = 0
Selim Cinekf418bb02020-05-04 17:16:58 -070074 private var playerWidthPlusPadding: Int = 0
Selim Cinek2de5ebb2020-05-20 15:39:03 -070075 private var desiredHostState: MediaHostState? = null
Robert Snoeberger52dfb622020-05-21 16:31:29 -040076 private val mediaCarousel: HorizontalScrollView
77 val mediaFrame: ViewGroup
Selim Cinek2de5ebb2020-05-20 15:39:03 -070078 val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf()
Robert Snoebergerd4efdae2020-06-12 15:42:25 -040079 private val mediaData: MutableMap<String, MediaData> = mutableMapOf()
Selim Cinek9e517f72020-04-28 12:24:34 -070080 private val mediaContent: ViewGroup
Robert Snoeberger52dfb622020-05-21 16:31:29 -040081 private val pageIndicator: PageIndicator
82 private val gestureDetector: GestureDetectorCompat
Robert Snoeberger70d0d6b2020-05-14 16:47:02 -040083 private val visualStabilityCallback: VisualStabilityManager.Callback
Selim Cinekf418bb02020-05-04 17:16:58 -070084 private var activeMediaIndex: Int = 0
Selim Cinek0260c752020-05-11 16:03:52 -070085 private var needsReordering: Boolean = false
Selim Cinekf418bb02020-05-04 17:16:58 -070086 private var scrollIntoCurrentMedia: Int = 0
Selim Cinek54809622020-04-30 19:04:44 -070087 private var currentlyExpanded = true
Selim Cinek9e517f72020-04-28 12:24:34 -070088 set(value) {
89 if (field != value) {
90 field = value
91 for (player in mediaPlayers.values) {
92 player.setListening(field)
93 }
94 }
95 }
Selim Cinekf418bb02020-05-04 17:16:58 -070096 private val scrollChangedListener = object : View.OnScrollChangeListener {
Robert Snoebergereb49e942020-05-12 16:31:09 -040097 override fun onScrollChange(
98 v: View?,
99 scrollX: Int,
100 scrollY: Int,
101 oldScrollX: Int,
102 oldScrollY: Int
103 ) {
Selim Cinekf418bb02020-05-04 17:16:58 -0700104 if (playerWidthPlusPadding == 0) {
105 return
106 }
107 onMediaScrollingChanged(scrollX / playerWidthPlusPadding,
108 scrollX % playerWidthPlusPadding)
109 }
110 }
Robert Snoeberger52dfb622020-05-21 16:31:29 -0400111 private val gestureListener = object : GestureDetector.SimpleOnGestureListener() {
112 override fun onFling(
113 eStart: MotionEvent?,
114 eCurrent: MotionEvent?,
115 vX: Float,
116 vY: Float
117 ): Boolean {
118 return this@MediaViewManager.onFling(eStart, eCurrent, vX, vY)
119 }
120 }
121 private val touchListener = object : View.OnTouchListener {
122 override fun onTouch(view: View, motionEvent: MotionEvent?): Boolean {
123 return this@MediaViewManager.onTouch(view, motionEvent)
124 }
125 }
Robert Snoebergerd4efdae2020-06-12 15:42:25 -0400126 private val configListener = object : ConfigurationController.ConfigurationListener {
127 override fun onDensityOrFontScaleChanged() {
128 recreatePlayers()
129 }
130 }
Selim Cinek9e517f72020-04-28 12:24:34 -0700131
132 init {
Robert Snoeberger52dfb622020-05-21 16:31:29 -0400133 gestureDetector = GestureDetectorCompat(context, gestureListener)
134 mediaFrame = inflateMediaCarousel()
135 mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
136 pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
Selim Cinekf418bb02020-05-04 17:16:58 -0700137 mediaCarousel.setOnScrollChangeListener(scrollChangedListener)
Robert Snoeberger52dfb622020-05-21 16:31:29 -0400138 mediaCarousel.setOnTouchListener(touchListener)
139 mediaCarousel.setOverScrollMode(View.OVER_SCROLL_NEVER)
Selim Cinek9e517f72020-04-28 12:24:34 -0700140 mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
Robert Snoebergerd4efdae2020-06-12 15:42:25 -0400141 configurationController.addCallback(configListener)
Selim Cinek0260c752020-05-11 16:03:52 -0700142 visualStabilityCallback = VisualStabilityManager.Callback {
143 if (needsReordering) {
144 needsReordering = false
145 reorderAllPlayers()
146 }
147 // Let's reset our scroll position
148 mediaCarousel.scrollX = 0
149 }
150 visualStabilityManager.addReorderingAllowedCallback(visualStabilityCallback,
151 true /* persistent */)
Selim Cinek9e517f72020-04-28 12:24:34 -0700152 mediaManager.addListener(object : MediaDataManager.Listener {
Beth Thibodeauf55bc6a2020-05-20 02:01:31 -0400153 override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
Robert Snoebergerd4efdae2020-06-12 15:42:25 -0400154 oldKey?.let { mediaData.remove(it) }
155 mediaData.put(key, data)
156 addOrUpdatePlayer(key, oldKey, data)
Selim Cinek9e517f72020-04-28 12:24:34 -0700157 }
158
159 override fun onMediaDataRemoved(key: String) {
Robert Snoebergerd4efdae2020-06-12 15:42:25 -0400160 mediaData.remove(key)
161 removePlayer(key)
Selim Cinek9e517f72020-04-28 12:24:34 -0700162 }
163 })
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700164 mediaHostStatesManager.addCallback(object : MediaHostStatesManager.Callback {
165 override fun onHostStateChanged(location: Int, mediaHostState: MediaHostState) {
166 if (location == desiredLocation) {
167 onDesiredLocationChanged(desiredLocation, mediaHostState, animate = false)
168 }
169 }
170 })
Selim Cinek9e517f72020-04-28 12:24:34 -0700171 }
172
Robert Snoeberger52dfb622020-05-21 16:31:29 -0400173 private fun inflateMediaCarousel(): ViewGroup {
Selim Cinekf418bb02020-05-04 17:16:58 -0700174 return LayoutInflater.from(context).inflate(R.layout.media_carousel,
Robert Snoeberger52dfb622020-05-21 16:31:29 -0400175 UniqueObjectHostView(context), false) as ViewGroup
Selim Cinek9e517f72020-04-28 12:24:34 -0700176 }
177
178 private fun reorderAllPlayers() {
179 for (mediaPlayer in mediaPlayers.values) {
Robert Snoebergereb49e942020-05-12 16:31:09 -0400180 val view = mediaPlayer.view?.player
Selim Cinek9e517f72020-04-28 12:24:34 -0700181 if (mediaPlayer.isPlaying && mediaContent.indexOfChild(view) != 0) {
182 mediaContent.removeView(view)
183 mediaContent.addView(view, 0)
184 }
185 }
186 updateMediaPaddings()
Selim Cinekf418bb02020-05-04 17:16:58 -0700187 updatePlayerVisibilities()
188 }
189
190 private fun onMediaScrollingChanged(newIndex: Int, scrollInAmount: Int) {
191 val wasScrolledIn = scrollIntoCurrentMedia != 0
192 scrollIntoCurrentMedia = scrollInAmount
193 val nowScrolledIn = scrollIntoCurrentMedia != 0
194 if (newIndex != activeMediaIndex || wasScrolledIn != nowScrolledIn) {
195 activeMediaIndex = newIndex
196 updatePlayerVisibilities()
197 }
Robert Snoeberger52dfb622020-05-21 16:31:29 -0400198 val location = activeMediaIndex.toFloat() + if (playerWidthPlusPadding > 0)
199 scrollInAmount.toFloat() / playerWidthPlusPadding else 0f
200 pageIndicator.setLocation(location)
201 }
202
203 private fun onTouch(view: View, motionEvent: MotionEvent?): Boolean {
204 if (gestureDetector.onTouchEvent(motionEvent)) {
205 return true
206 }
207 if (motionEvent?.getAction() == MotionEvent.ACTION_UP) {
208 val pos = mediaCarousel.scrollX % playerWidthPlusPadding
209 if (pos > playerWidthPlusPadding / 2) {
210 mediaCarousel.smoothScrollBy(playerWidthPlusPadding - pos, 0)
211 } else {
212 mediaCarousel.smoothScrollBy(-1 * pos, 0)
213 }
214 return true
215 }
216 return view.onTouchEvent(motionEvent)
217 }
218
219 private fun onFling(
220 eStart: MotionEvent?,
221 eCurrent: MotionEvent?,
222 vX: Float,
223 vY: Float
224 ): Boolean {
225 if (vX * vX < 0.5 * vY * vY) {
226 return false
227 }
228 if (vX * vX < FLING_SLOP) {
229 return false
230 }
231 val pos = mediaCarousel.scrollX
232 val currentIndex = if (playerWidthPlusPadding > 0) pos / playerWidthPlusPadding else 0
233 var destIndex = if (vX <= 0) currentIndex + 1 else currentIndex
234 destIndex = Math.max(0, destIndex)
235 destIndex = Math.min(mediaContent.getChildCount() - 1, destIndex)
236 val view = mediaContent.getChildAt(destIndex)
237 mediaCarousel.smoothScrollTo(view.left, mediaCarousel.scrollY)
238 return true
Selim Cinekf418bb02020-05-04 17:16:58 -0700239 }
240
241 private fun updatePlayerVisibilities() {
242 val scrolledIn = scrollIntoCurrentMedia != 0
243 for (i in 0 until mediaContent.childCount) {
244 val view = mediaContent.getChildAt(i)
245 val visible = (i == activeMediaIndex) || ((i == (activeMediaIndex + 1)) && scrolledIn)
246 view.visibility = if (visible) View.VISIBLE else View.INVISIBLE
247 }
Selim Cinek9e517f72020-04-28 12:24:34 -0700248 }
249
Robert Snoebergerd4efdae2020-06-12 15:42:25 -0400250 private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData) {
Beth Thibodeauf55bc6a2020-05-20 02:01:31 -0400251 // If the key was changed, update entry
252 val oldData = mediaPlayers[oldKey]
253 if (oldData != null) {
254 val oldData = mediaPlayers.remove(oldKey)
255 mediaPlayers.put(key, oldData!!)
256 }
Selim Cinek9e517f72020-04-28 12:24:34 -0700257 var existingPlayer = mediaPlayers[key]
258 if (existingPlayer == null) {
Robert Snoeberger7cec5422020-05-29 17:09:14 -0400259 existingPlayer = mediaControlPanelFactory.get()
Robert Snoebergereb49e942020-05-12 16:31:09 -0400260 existingPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context),
261 mediaContent))
Selim Cinek9e517f72020-04-28 12:24:34 -0700262 mediaPlayers[key] = existingPlayer
263 val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
264 ViewGroup.LayoutParams.WRAP_CONTENT)
Robert Snoebergereb49e942020-05-12 16:31:09 -0400265 existingPlayer.view?.player?.setLayoutParams(lp)
Selim Cinek54809622020-04-30 19:04:44 -0700266 existingPlayer.setListening(currentlyExpanded)
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700267 updatePlayerToState(existingPlayer, noAnimation = true)
Selim Cinek9e517f72020-04-28 12:24:34 -0700268 if (existingPlayer.isPlaying) {
Robert Snoebergereb49e942020-05-12 16:31:09 -0400269 mediaContent.addView(existingPlayer.view?.player, 0)
Selim Cinek9e517f72020-04-28 12:24:34 -0700270 } else {
Robert Snoebergereb49e942020-05-12 16:31:09 -0400271 mediaContent.addView(existingPlayer.view?.player)
Selim Cinek9e517f72020-04-28 12:24:34 -0700272 }
273 } else if (existingPlayer.isPlaying &&
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700274 mediaContent.indexOfChild(existingPlayer.view?.player) != 0) {
Selim Cinek9e517f72020-04-28 12:24:34 -0700275 if (visualStabilityManager.isReorderingAllowed) {
Robert Snoebergereb49e942020-05-12 16:31:09 -0400276 mediaContent.removeView(existingPlayer.view?.player)
277 mediaContent.addView(existingPlayer.view?.player, 0)
Selim Cinek9e517f72020-04-28 12:24:34 -0700278 } else {
Selim Cinek0260c752020-05-11 16:03:52 -0700279 needsReordering = true
Selim Cinek9e517f72020-04-28 12:24:34 -0700280 }
281 }
Robert Snoeberger7cec5422020-05-29 17:09:14 -0400282 existingPlayer?.bind(data)
Selim Cinek9e517f72020-04-28 12:24:34 -0700283 updateMediaPaddings()
Robert Snoeberger52dfb622020-05-21 16:31:29 -0400284 updatePageIndicator()
Robert Snoebergerd4efdae2020-06-12 15:42:25 -0400285 updatePlayerVisibilities()
286 mediaCarousel.requiresRemeasuring = true
287 }
288
289 private fun removePlayer(key: String) {
290 val removed = mediaPlayers.remove(key)
291 removed?.apply {
292 val beforeActive = mediaContent.indexOfChild(removed.view?.player) <=
293 activeMediaIndex
294 mediaContent.removeView(removed.view?.player)
295 removed.onDestroy()
296 updateMediaPaddings()
297 if (beforeActive) {
298 // also update the index here since the scroll below might not always lead
299 // to a scrolling changed
300 activeMediaIndex = Math.max(0, activeMediaIndex - 1)
301 mediaCarousel.scrollX = Math.max(mediaCarousel.scrollX -
302 playerWidthPlusPadding, 0)
303 }
304 updatePlayerVisibilities()
305 updatePageIndicator()
306 }
307 }
308
309 private fun recreatePlayers() {
310 // Note that this will scramble the order of players. Actively playing sessions will, at
311 // least, still be put in the front. If we want to maintain order, then more work is
312 // needed.
313 mediaData.forEach {
314 key, data ->
315 removePlayer(key)
316 addOrUpdatePlayer(key = key, oldKey = null, data = data)
317 }
Selim Cinek9e517f72020-04-28 12:24:34 -0700318 }
319
320 private fun updateMediaPaddings() {
321 val padding = context.resources.getDimensionPixelSize(R.dimen.qs_media_padding)
322 val childCount = mediaContent.childCount
323 for (i in 0 until childCount) {
324 val mediaView = mediaContent.getChildAt(i)
325 val desiredPaddingEnd = if (i == childCount - 1) 0 else padding
326 val layoutParams = mediaView.layoutParams as ViewGroup.MarginLayoutParams
327 if (layoutParams.marginEnd != desiredPaddingEnd) {
328 layoutParams.marginEnd = desiredPaddingEnd
329 mediaView.layoutParams = layoutParams
330 }
331 }
Selim Cinek9e517f72020-04-28 12:24:34 -0700332 }
333
Robert Snoeberger52dfb622020-05-21 16:31:29 -0400334 private fun updatePageIndicator() {
335 val numPages = mediaContent.getChildCount()
336 pageIndicator.setNumPages(numPages, Color.WHITE)
337 if (numPages == 1) {
338 pageIndicator.setLocation(0f)
339 }
340 }
341
Selim Cinekf418bb02020-05-04 17:16:58 -0700342 /**
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700343 * Set a new interpolated state for all players. This is a state that is usually controlled
344 * by a finger movement where the user drags from one state to the next.
Selim Cinekf418bb02020-05-04 17:16:58 -0700345 */
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700346 fun setCurrentState(
347 @MediaLocation startLocation: Int,
348 @MediaLocation endLocation: Int,
349 progress: Float,
350 immediately: Boolean
351 ) {
Robert Snoeberger52dfb622020-05-21 16:31:29 -0400352 // Hack: Since the indicator doesn't move with the player expansion, just make it disappear
353 // and then reappear at the end.
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700354 pageIndicator.alpha = if (progress == 1f || progress == 0f) 1f else 0f
355 if (startLocation != currentStartLocation ||
356 endLocation != currentEndLocation ||
357 progress != currentTransitionProgress ||
358 immediately
359 ) {
360 currentStartLocation = startLocation
361 currentEndLocation = endLocation
362 currentTransitionProgress = progress
363 for (mediaPlayer in mediaPlayers.values) {
364 updatePlayerToState(mediaPlayer, immediately)
365 }
Selim Cinek9e517f72020-04-28 12:24:34 -0700366 }
Selim Cinek9e517f72020-04-28 12:24:34 -0700367 }
Selim Cinek3df592e2020-04-28 13:51:43 -0700368
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700369 private fun updatePlayerToState(mediaPlayer: MediaControlPanel, noAnimation: Boolean) {
370 mediaPlayer.mediaViewController.setCurrentState(
371 startLocation = currentStartLocation,
372 endLocation = currentEndLocation,
373 transitionProgress = currentTransitionProgress,
374 applyImmediately = noAnimation)
375 }
376
Selim Cinek3df592e2020-04-28 13:51:43 -0700377 /**
Selim Cinekf418bb02020-05-04 17:16:58 -0700378 * The desired location of this view has changed. We should remeasure the view to match
379 * the new bounds and kick off bounds animations if necessary.
380 * If an animation is happening, an animation is kicked of externally, which sets a new
381 * current state until we reach the targetState.
382 *
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700383 * @param desiredLocation the location we're going to
384 * @param desiredHostState the target state we're transitioning to
Selim Cinek3df592e2020-04-28 13:51:43 -0700385 * @param animate should this be animated
386 */
Robert Snoebergereb49e942020-05-12 16:31:09 -0400387 fun onDesiredLocationChanged(
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700388 desiredLocation: Int,
389 desiredHostState: MediaHostState?,
Robert Snoebergereb49e942020-05-12 16:31:09 -0400390 animate: Boolean,
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700391 duration: Long = 200,
392 startDelay: Long = 0
Robert Snoebergereb49e942020-05-12 16:31:09 -0400393 ) {
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700394 desiredHostState?.let {
Selim Cinek54809622020-04-30 19:04:44 -0700395 // This is a hosting view, let's remeasure our players
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700396 this.desiredLocation = desiredLocation
397 this.desiredHostState = it
398 currentlyExpanded = it.expansion > 0
Selim Cinekf418bb02020-05-04 17:16:58 -0700399 for (mediaPlayer in mediaPlayers.values) {
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700400 if (animate) {
401 mediaPlayer.mediaViewController.animatePendingStateChange(
402 duration = duration,
403 delay = startDelay)
404 }
405 mediaPlayer.mediaViewController.onLocationPreChange(desiredLocation)
Selim Cinekf418bb02020-05-04 17:16:58 -0700406 }
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700407 updateCarouselSize()
Selim Cinek3df592e2020-04-28 13:51:43 -0700408 }
Selim Cinek3df592e2020-04-28 13:51:43 -0700409 }
410
Selim Cinek54809622020-04-30 19:04:44 -0700411 /**
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700412 * Update the size of the carousel, remeasuring it if necessary.
Selim Cinek54809622020-04-30 19:04:44 -0700413 */
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700414 private fun updateCarouselSize() {
415 val width = desiredHostState?.measurementInput?.width ?: 0
416 val height = desiredHostState?.measurementInput?.height ?: 0
417 if (width != carouselMeasureWidth && width != 0 ||
418 height != carouselMeasureWidth && height != 0) {
419 carouselMeasureWidth = width
420 carouselMeasureHeight = height
421 playerWidthPlusPadding = carouselMeasureWidth + context.resources.getDimensionPixelSize(
422 R.dimen.qs_media_padding)
423 // The player width has changed, let's update the scroll position to make sure
424 // it's still at the same place
425 var newScroll = activeMediaIndex * playerWidthPlusPadding
426 if (scrollIntoCurrentMedia > playerWidthPlusPadding) {
427 newScroll += playerWidthPlusPadding -
428 (scrollIntoCurrentMedia - playerWidthPlusPadding)
429 } else {
430 newScroll += scrollIntoCurrentMedia
Robert Snoebergereb49e942020-05-12 16:31:09 -0400431 }
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700432 mediaCarousel.scrollX = newScroll
433 // Let's remeasure the carousel
434 val widthSpec = desiredHostState?.measurementInput?.widthMeasureSpec ?: 0
435 val heightSpec = desiredHostState?.measurementInput?.heightMeasureSpec ?: 0
436 mediaCarousel.measure(widthSpec, heightSpec)
437 mediaCarousel.layout(0, 0, width, mediaCarousel.measuredHeight)
Selim Cinekf418bb02020-05-04 17:16:58 -0700438 }
439 }
Robert Snoebergereb49e942020-05-12 16:31:09 -0400440}