Loading packages/SystemUI/res/layout/media_carousel.xml +26 −9 Original line number Diff line number Diff line Loading @@ -16,10 +16,17 @@ --> <!-- Carousel for media controls --> <com.android.systemui.media.UnboundHorizontalScrollView <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:clipChildren="false" android:clipToPadding="false" > <com.android.systemui.media.UnboundHorizontalScrollView android:id="@+id/media_carousel_scroller" android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollbars="none" android:clipChildren="false" android:clipToPadding="false" Loading @@ -35,3 +42,13 @@ <!-- QSMediaPlayers will be added here dynamically --> </LinearLayout> </com.android.systemui.media.UnboundHorizontalScrollView> <com.android.systemui.qs.PageIndicator android:id="@+id/media_page_indicator" android:layout_width="wrap_content" android:layout_height="48dp" android:layout_marginBottom="4dp" android:layout_gravity="center_horizontal|bottom" android:gravity="center" android:tint="@color/media_primary_text" /> </FrameLayout> packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +2 −2 Original line number Diff line number Diff line Loading @@ -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 { Loading packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt +88 −4 Original line number Diff line number Diff line 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 Loading @@ -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. Loading @@ -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<String, MediaControlPanel> = mutableMapOf() private val pageIndicator: PageIndicator private val gestureDetector: GestureDetectorCompat private val visualStabilityCallback: VisualStabilityManager.Callback private var activeMediaIndex: Int = 0 private var needsReordering: Boolean = false Loading Loading @@ -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) { Loading Loading @@ -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() { Loading @@ -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() { Loading Loading @@ -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) { Loading @@ -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. Loading @@ -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 Loading packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java +9 −4 Original line number Diff line number Diff line Loading @@ -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"); Loading @@ -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); Loading Loading
packages/SystemUI/res/layout/media_carousel.xml +26 −9 Original line number Diff line number Diff line Loading @@ -16,10 +16,17 @@ --> <!-- Carousel for media controls --> <com.android.systemui.media.UnboundHorizontalScrollView <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:clipChildren="false" android:clipToPadding="false" > <com.android.systemui.media.UnboundHorizontalScrollView android:id="@+id/media_carousel_scroller" android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollbars="none" android:clipChildren="false" android:clipToPadding="false" Loading @@ -35,3 +42,13 @@ <!-- QSMediaPlayers will be added here dynamically --> </LinearLayout> </com.android.systemui.media.UnboundHorizontalScrollView> <com.android.systemui.qs.PageIndicator android:id="@+id/media_page_indicator" android:layout_width="wrap_content" android:layout_height="48dp" android:layout_marginBottom="4dp" android:layout_gravity="center_horizontal|bottom" android:gravity="center" android:tint="@color/media_primary_text" /> </FrameLayout>
packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +2 −2 Original line number Diff line number Diff line Loading @@ -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 { Loading
packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt +88 −4 Original line number Diff line number Diff line 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 Loading @@ -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. Loading @@ -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<String, MediaControlPanel> = mutableMapOf() private val pageIndicator: PageIndicator private val gestureDetector: GestureDetectorCompat private val visualStabilityCallback: VisualStabilityManager.Callback private var activeMediaIndex: Int = 0 private var needsReordering: Boolean = false Loading Loading @@ -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) { Loading Loading @@ -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() { Loading @@ -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() { Loading Loading @@ -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) { Loading @@ -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. Loading @@ -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 Loading
packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java +9 −4 Original line number Diff line number Diff line Loading @@ -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"); Loading @@ -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); Loading