Merge "Correct RTL layout of media players" into rvc-dev
diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml
index 5b86492..07bbb8f 100644
--- a/packages/SystemUI/res/layout/media_view.xml
+++ b/packages/SystemUI/res/layout/media_view.xml
@@ -26,6 +26,7 @@
     android:gravity="center_horizontal|fill_vertical"
     android:background="@drawable/qs_media_background">
 
+    <!-- As per Material Design on Biderectionality, this is forced to LTR in code -->
     <FrameLayout
         android:id="@+id/notification_media_progress_time"
         android:layout_width="0dp"
@@ -36,7 +37,7 @@
             android:id="@+id/media_elapsed_time"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_alignParentLeft="true"
+            android:layout_alignParentStart="true"
             android:fontFamily="@*android:string/config_bodyFontFamily"
             android:textColor="@color/media_primary_text"
             android:gravity="start"
@@ -46,13 +47,36 @@
             android:id="@+id/media_total_time"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_alignParentRight="true"
+            android:layout_alignParentEnd="true"
             android:fontFamily="@*android:string/config_bodyFontFamily"
             android:textColor="@color/media_primary_text"
             android:gravity="end"
             android:textSize="14sp" />
     </FrameLayout>
 
+    <!--  Actions must be ordered left-to-right even in RTL layout.  However, they appear in a chain
+    with the album art and the title, and must as a group appear at the end of that chain.  This is
+    accomplished by having the guidebox (an invisible view that is positioned around all 5 actions)
+    in the chain with the album art and the title.  The actions are in a LTR chain bounded by that
+    guidebox, and the ambiguity of how wide the guidebox should be is resolved by using a barrier
+    which forces it's starting edge to be as far to the end as possible while fitting the actions.
+    -->
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/media_action_barrier"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:orientation="vertical"
+        app:barrierDirection="start"
+        />
+
+    <View
+        android:id="@+id/media_action_guidebox"
+        android:layout_width="0dp"
+        android:layout_height="48dp"
+        android:layout_marginTop="16dp"
+        android:visibility="invisible"
+        />
+
     <ImageButton
         android:id="@+id/action0"
         style="@style/MediaPlayer.Button"
@@ -98,16 +122,16 @@
         android:background="@drawable/qs_media_light_source"
         android:orientation="horizontal"
         android:forceHasOverlappingRendering="false"
-        android:paddingLeft="12dp"
+        android:paddingStart="12dp"
         android:paddingTop="6dp"
-        android:paddingRight="12dp"
+        android:paddingEnd="12dp"
         android:paddingBottom="6dp">
 
         <ImageView
             android:id="@+id/media_seamless_image"
             android:layout_width="@dimen/qs_seamless_icon_size"
             android:layout_height="@dimen/qs_seamless_icon_size"
-            android:layout_marginRight="8dp"
+            android:layout_marginEnd="8dp"
             android:tint="@color/media_primary_text"
             android:src="@*android:drawable/ic_media_seamless" />
 
@@ -119,6 +143,7 @@
             android:singleLine="true"
             android:text="@*android:string/ext_media_seamless_action"
             android:textColor="@color/media_primary_text"
+            android:textDirection="locale"
             android:textSize="14sp" />
     </LinearLayout>
 
@@ -140,6 +165,7 @@
         />
 
     <!-- Seek Bar -->
+    <!-- As per Material Design on Biderectionality, this is forced to LTR in code -->
     <SeekBar
         android:id="@+id/media_progress_bar"
         style="@android:style/Widget.ProgressBar.Horizontal"
@@ -162,6 +188,7 @@
         android:layout_height="wrap_content"
         android:singleLine="true"
         android:fontFamily="@*android:string/config_headlineFontFamily"
+        android:textDirection="locale"
         android:textSize="14sp" />
 
     <!-- Song name -->
@@ -172,6 +199,7 @@
         android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
         android:singleLine="true"
         android:textColor="@color/media_primary_text"
+        android:textDirection="locale"
         android:textSize="16sp" />
 
     <!-- Artist name -->
@@ -182,6 +210,7 @@
         android:fontFamily="@*android:string/config_headlineFontFamily"
         android:singleLine="true"
         android:textColor="@color/media_secondary_text"
+        android:textDirection="locale"
         android:textSize="14sp" />
 
     <com.android.internal.widget.CachingIconView
diff --git a/packages/SystemUI/res/xml/media_collapsed.xml b/packages/SystemUI/res/xml/media_collapsed.xml
index 0e886d6..811e0e3 100644
--- a/packages/SystemUI/res/xml/media_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_collapsed.xml
@@ -85,7 +85,7 @@
         app:layout_constraintTop_toBottomOf="@id/app_name"
         app:layout_constraintBottom_toTopOf="@id/header_artist"
         app:layout_constraintStart_toEndOf="@id/album_art"
-        app:layout_constraintEnd_toStartOf="@id/action0"
+        app:layout_constraintEnd_toStartOf="@id/media_action_barrier"
         app:layout_constraintHorizontal_bias="0"/>
 
     <!-- Artist name -->
@@ -97,7 +97,7 @@
         android:layout_marginBottom="24dp"
         app:layout_constraintTop_toBottomOf="@id/header_title"
         app:layout_constraintStart_toStartOf="@id/header_title"
-        app:layout_constraintEnd_toStartOf="@id/action0"
+        app:layout_constraintEnd_toStartOf="@id/media_action_barrier"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintHorizontal_bias="0"/>
 
@@ -128,15 +128,37 @@
         />
 
     <Constraint
+        android:id="@+id/media_action_barrier"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:orientation="vertical"
+        app:layout_constraintTop_toTopOf="parent"
+        app:barrierDirection="start"
+        app:constraint_referenced_ids="media_action_guidebox,action0,action1,action2,action3,action4"
+        />
+
+    <Constraint
+        android:id="@+id/media_action_guidebox"
+        android:layout_width="0dp"
+        android:layout_height="48dp"
+        android:layout_marginTop="18dp"
+        android:visibility="invisible"
+        app:layout_constraintTop_toBottomOf="@id/app_name"
+        app:layout_constraintStart_toEndOf="@id/header_title"
+        app:layout_constraintEnd_toEndOf="parent"
+        />
+
+    <Constraint
         android:id="@+id/action0"
         android:layout_width="48dp"
         android:layout_height="48dp"
         android:layout_marginStart="4dp"
-        android:layout_marginTop="16dp"
+        android:layout_marginEnd="4dp"
+        android:layout_marginTop="18dp"
         android:visibility="gone"
         app:layout_constraintHorizontal_chainStyle="packed"
         app:layout_constraintTop_toBottomOf="@id/app_name"
-        app:layout_constraintLeft_toRightOf="@id/header_title"
+        app:layout_constraintLeft_toLeftOf="@id/media_action_guidebox"
         app:layout_constraintRight_toLeftOf="@id/action1"
         >
     </Constraint>
@@ -188,9 +210,10 @@
         android:layout_marginEnd="4dp"
         android:visibility="gone"
         android:layout_marginTop="18dp"
+        app:layout_constraintHorizontal_chainStyle="packed"
         app:layout_constraintTop_toBottomOf="@id/app_name"
         app:layout_constraintLeft_toRightOf="@id/action3"
-        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintRight_toRightOf="@id/media_action_guidebox"
         >
     </Constraint>
 </ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_expanded.xml b/packages/SystemUI/res/xml/media_expanded.xml
index 9b4caa4..8432abc 100644
--- a/packages/SystemUI/res/xml/media_expanded.xml
+++ b/packages/SystemUI/res/xml/media_expanded.xml
@@ -182,6 +182,7 @@
         android:layout_marginStart="4dp"
         android:layout_marginEnd="4dp"
         android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
+        app:layout_constraintHorizontal_chainStyle="packed"
         app:layout_constraintLeft_toRightOf="@id/action3"
         app:layout_constraintRight_toRightOf="parent"
         app:layout_constraintTop_toTopOf="@id/action0"
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
index 90961db..fc22c02 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
@@ -17,9 +17,11 @@
 package com.android.systemui.media
 
 import android.content.Context
+import android.content.res.Configuration
 import android.graphics.PointF
 import androidx.constraintlayout.widget.ConstraintSet
 import com.android.systemui.R
+import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.animation.MeasurementOutput
 import com.android.systemui.util.animation.TransitionLayout
 import com.android.systemui.util.animation.TransitionLayoutController
@@ -32,6 +34,7 @@
  */
 class MediaViewController @Inject constructor(
     context: Context,
+    private val configurationController: ConfigurationController,
     private val mediaHostStatesManager: MediaHostStatesManager
 ) {
 
@@ -56,12 +59,14 @@
      * The ending location of the view where it ends when all animations and transitions have
      * finished
      */
+    @MediaLocation
     private var currentEndLocation: Int = -1
 
     /**
      * The ending location of the view where it ends when all animations and transitions have
      * finished
      */
+    @MediaLocation
     private var currentStartLocation: Int = -1
 
     /**
@@ -91,10 +96,31 @@
     var currentHeight: Int = 0
 
     /**
+     * A callback for RTL config changes
+     */
+    private val configurationListener = object : ConfigurationController.ConfigurationListener {
+        override fun onConfigChanged(newConfig: Configuration?) {
+            // Because the TransitionLayout is not always attached (and calculates/caches layout
+            // results regardless of attach state), we have to force the layoutDirection of the view
+            // to the correct value for the user's current locale to ensure correct recalculation
+            // when/after calling refreshState()
+            newConfig?.apply {
+                if (transitionLayout?.rawLayoutDirection != layoutDirection) {
+                    transitionLayout?.layoutDirection = layoutDirection
+                    refreshState()
+                }
+            }
+        }
+    }
+
+    /**
      * A callback for media state changes
      */
     val stateCallback = object : MediaHostStatesManager.Callback {
-        override fun onHostStateChanged(location: Int, mediaHostState: MediaHostState) {
+        override fun onHostStateChanged(
+            @MediaLocation location: Int,
+            mediaHostState: MediaHostState
+        ) {
             if (location == currentEndLocation || location == currentStartLocation) {
                 setCurrentState(currentStartLocation,
                         currentEndLocation,
@@ -125,6 +151,7 @@
             currentHeight = height
             sizeChangedListener.invoke()
         }
+        configurationController.addCallback(configurationListener)
     }
 
     /**
@@ -132,6 +159,7 @@
      */
     fun onDestroy() {
         mediaHostStatesManager.removeController(this)
+        configurationController.removeCallback(configurationListener)
     }
 
     private fun ensureAllMeasurements() {
@@ -346,7 +374,7 @@
             // Let's clear all of our measurements and recreate them!
             viewStates.clear()
             setCurrentState(currentStartLocation, currentEndLocation, currentTransitionProgress,
-                    applyImmediately = false)
+                    applyImmediately = true)
         }
         firstRefresh = false
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index 9ede083..600fdc2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -48,6 +48,7 @@
 
     // Seek bar
     val seekBar = itemView.requireViewById<SeekBar>(R.id.media_progress_bar)
+    val progressTimes = itemView.requireViewById<ViewGroup>(R.id.notification_media_progress_time)
     val elapsedTimeView = itemView.requireViewById<TextView>(R.id.media_elapsed_time)
     val totalTimeView = itemView.requireViewById<TextView>(R.id.media_total_time)
 
@@ -93,8 +94,16 @@
          * @param parent Parent of inflated view.
          */
         @JvmStatic fun create(inflater: LayoutInflater, parent: ViewGroup): PlayerViewHolder {
-            val v = inflater.inflate(R.layout.media_view, parent, false)
-            return PlayerViewHolder(v)
+            val mediaView = inflater.inflate(R.layout.media_view, parent, false)
+            // Because this media view (a TransitionLayout) is used to measure and layout the views
+            // in various states before being attached to its parent, we can't depend on the default
+            // LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
+            mediaView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
+            return PlayerViewHolder(mediaView).apply {
+                // Media playback is in the direction of tape, not time, so it stays LTR
+                seekBar.layoutDirection = View.LAYOUT_DIRECTION_LTR
+                progressTimes.layoutDirection = View.LAYOUT_DIRECTION_LTR
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt b/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt
index 5b6444d..d6e7a8b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt
@@ -70,7 +70,10 @@
     }
 
     override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) {
-        if (child?.measuredWidth == 0 || measuredWidth == 0 || child?.requiresRemeasuring == true) {
+        if (child == null) {
+            throw IllegalArgumentException("child must be non-null")
+        }
+        if (child.measuredWidth == 0 || measuredWidth == 0 || child.requiresRemeasuring == true) {
             super.addView(child, index, params)
             return
         }
@@ -78,11 +81,13 @@
         // right size when being attached to this view
         invalidate()
         addViewInLayout(child, index, params, true /* preventRequestLayout */)
+        // RTL properties are normally resolved in onMeasure(), which we are intentionally skipping
+        child.resolveRtlPropertiesIfNeeded()
         val left = paddingLeft
         val top = paddingTop
         val paddingHorizontal = paddingStart + paddingEnd
         val paddingVertical = paddingTop + paddingBottom
-        child!!.layout(left,
+        child.layout(left,
                 top,
                 left + measuredWidth - paddingHorizontal,
                 top + measuredHeight - paddingVertical)