diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml
index 03e74676f03e4..dc917316bef1b 100644
--- a/packages/SystemUI/res/layout/media_carousel.xml
+++ b/packages/SystemUI/res/layout/media_carousel.xml
@@ -16,22 +16,39 @@
-->
-
-
-
-
-
+
+
+
+
+
+
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 3e7661ac0dd91..cce1d3efd6e54 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -58,7 +58,7 @@ class MediaHierarchyManager @Inject constructor(
private var rootOverlay: ViewGroupOverlay? = null
private lateinit var currentState: MediaState
private val mediaCarousel
- get() = mediaViewManager.mediaCarousel
+ get() = mediaViewManager.mediaFrame
private var animationStartState: MediaState? = null
private var statusbarState: Int = statusBarStateController.state
private var animator = ValueAnimator.ofFloat(0.0f, 1.0f).apply {
@@ -424,4 +424,4 @@ class MediaHierarchyManager @Inject constructor(
*/
const val IN_OVERLAY = -1000
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
index 17e8404fe705f..16302d1632b5b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
@@ -1,15 +1,20 @@
package com.android.systemui.media
import android.content.Context
+import android.graphics.Color
import android.view.LayoutInflater
+import android.view.GestureDetector
+import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.HorizontalScrollView
import android.widget.LinearLayout
+import androidx.core.view.GestureDetectorCompat
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.PageIndicator
import com.android.systemui.statusbar.notification.VisualStabilityManager
import com.android.systemui.util.animation.MeasurementOutput
import com.android.systemui.util.animation.UniqueObjectHostView
@@ -18,6 +23,8 @@ import java.util.concurrent.Executor
import javax.inject.Inject
import javax.inject.Singleton
+private const val FLING_SLOP = 1000000
+
/**
* Class that is responsible for keeping the view carousel up to date.
* This also handles changes in state and applies them to the media carousel like the expansion.
@@ -35,9 +42,12 @@ class MediaViewManager @Inject constructor(
private var playerWidthPlusPadding: Int = 0
private var desiredState: MediaHost.MediaHostState? = null
private var currentState: MediaState? = null
- val mediaCarousel: HorizontalScrollView
+ private val mediaCarousel: HorizontalScrollView
+ val mediaFrame: ViewGroup
private val mediaContent: ViewGroup
private val mediaPlayers: MutableMap = mutableMapOf()
+ private val pageIndicator: PageIndicator
+ private val gestureDetector: GestureDetectorCompat
private val visualStabilityCallback: VisualStabilityManager.Callback
private var activeMediaIndex: Int = 0
private var needsReordering: Boolean = false
@@ -66,10 +76,30 @@ class MediaViewManager @Inject constructor(
scrollX % playerWidthPlusPadding)
}
}
+ private val gestureListener = object : GestureDetector.SimpleOnGestureListener() {
+ override fun onFling(
+ eStart: MotionEvent?,
+ eCurrent: MotionEvent?,
+ vX: Float,
+ vY: Float
+ ): Boolean {
+ return this@MediaViewManager.onFling(eStart, eCurrent, vX, vY)
+ }
+ }
+ private val touchListener = object : View.OnTouchListener {
+ override fun onTouch(view: View, motionEvent: MotionEvent?): Boolean {
+ return this@MediaViewManager.onTouch(view, motionEvent)
+ }
+ }
init {
- mediaCarousel = inflateMediaCarousel()
+ gestureDetector = GestureDetectorCompat(context, gestureListener)
+ mediaFrame = inflateMediaCarousel()
+ mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
+ pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
mediaCarousel.setOnScrollChangeListener(scrollChangedListener)
+ mediaCarousel.setOnTouchListener(touchListener)
+ mediaCarousel.setOverScrollMode(View.OVER_SCROLL_NEVER)
mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
visualStabilityCallback = VisualStabilityManager.Callback {
if (needsReordering) {
@@ -103,14 +133,15 @@ class MediaViewManager @Inject constructor(
playerWidthPlusPadding, 0)
}
updatePlayerVisibilities()
+ updatePageIndicator()
}
}
})
}
- private fun inflateMediaCarousel(): HorizontalScrollView {
+ private fun inflateMediaCarousel(): ViewGroup {
return LayoutInflater.from(context).inflate(R.layout.media_carousel,
- UniqueObjectHostView(context), false) as HorizontalScrollView
+ UniqueObjectHostView(context), false) as ViewGroup
}
private fun reorderAllPlayers() {
@@ -133,6 +164,47 @@ class MediaViewManager @Inject constructor(
activeMediaIndex = newIndex
updatePlayerVisibilities()
}
+ val location = activeMediaIndex.toFloat() + if (playerWidthPlusPadding > 0)
+ scrollInAmount.toFloat() / playerWidthPlusPadding else 0f
+ pageIndicator.setLocation(location)
+ }
+
+ private fun onTouch(view: View, motionEvent: MotionEvent?): Boolean {
+ if (gestureDetector.onTouchEvent(motionEvent)) {
+ return true
+ }
+ if (motionEvent?.getAction() == MotionEvent.ACTION_UP) {
+ val pos = mediaCarousel.scrollX % playerWidthPlusPadding
+ if (pos > playerWidthPlusPadding / 2) {
+ mediaCarousel.smoothScrollBy(playerWidthPlusPadding - pos, 0)
+ } else {
+ mediaCarousel.smoothScrollBy(-1 * pos, 0)
+ }
+ return true
+ }
+ return view.onTouchEvent(motionEvent)
+ }
+
+ private fun onFling(
+ eStart: MotionEvent?,
+ eCurrent: MotionEvent?,
+ vX: Float,
+ vY: Float
+ ): Boolean {
+ if (vX * vX < 0.5 * vY * vY) {
+ return false
+ }
+ if (vX * vX < FLING_SLOP) {
+ return false
+ }
+ val pos = mediaCarousel.scrollX
+ val currentIndex = if (playerWidthPlusPadding > 0) pos / playerWidthPlusPadding else 0
+ var destIndex = if (vX <= 0) currentIndex + 1 else currentIndex
+ destIndex = Math.max(0, destIndex)
+ destIndex = Math.min(mediaContent.getChildCount() - 1, destIndex)
+ val view = mediaContent.getChildAt(destIndex)
+ mediaCarousel.smoothScrollTo(view.left, mediaCarousel.scrollY)
+ return true
}
private fun updatePlayerVisibilities() {
@@ -176,6 +248,7 @@ class MediaViewManager @Inject constructor(
// motion model
existingPlayer.view?.player?.progress = currentState?.expansion ?: 0.0f
updateMediaPaddings()
+ updatePageIndicator()
}
private fun updatePlayerToCurrentState(existingPlayer: MediaControlPanel) {
@@ -199,6 +272,14 @@ class MediaViewManager @Inject constructor(
}
}
+ private fun updatePageIndicator() {
+ val numPages = mediaContent.getChildCount()
+ pageIndicator.setNumPages(numPages, Color.WHITE)
+ if (numPages == 1) {
+ pageIndicator.setLocation(0f)
+ }
+ }
+
/**
* Set the current state of a view. This is updated often during animations and we shouldn't
* do anything expensive.
@@ -206,6 +287,9 @@ class MediaViewManager @Inject constructor(
fun setCurrentState(state: MediaState) {
currentState = state
currentlyExpanded = state.expansion > 0
+ // Hack: Since the indicator doesn't move with the player expansion, just make it disappear
+ // and then reappear at the end.
+ pageIndicator.alpha = if (state.expansion == 1f || state.expansion == 0f) 1f else 0f
for (mediaPlayer in mediaPlayers.values) {
val view = mediaPlayer.view?.player
view?.progress = state.expansion
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
index d5c5ba401e997..69337f988080c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
@@ -45,6 +45,15 @@ public class PageIndicator extends ViewGroup {
}
public void setNumPages(int numPages) {
+ TypedArray array = getContext().obtainStyledAttributes(
+ new int[]{android.R.attr.colorControlActivated});
+ int color = array.getColor(0, 0);
+ array.recycle();
+ setNumPages(numPages, color);
+ }
+
+ /** Oveload of setNumPages that allows the indicator color to be specified.*/
+ public void setNumPages(int numPages, int color) {
setVisibility(numPages > 1 ? View.VISIBLE : View.GONE);
if (mAnimating) {
Log.w(TAG, "setNumPages during animation");
@@ -52,10 +61,6 @@ public class PageIndicator extends ViewGroup {
while (numPages < getChildCount()) {
removeViewAt(getChildCount() - 1);
}
- TypedArray array = getContext().obtainStyledAttributes(
- new int[]{android.R.attr.colorControlActivated});
- int color = array.getColor(0, 0);
- array.recycle();
while (numPages > getChildCount()) {
ImageView v = new ImageView(mContext);
v.setImageResource(R.drawable.minor_a_b);