Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8c12bdb0 authored by Derek Jedral's avatar Derek Jedral Committed by Android (Google) Code Review
Browse files

Merge "Add suggested device to UMO" into main

parents edcde333 a86afaa4
Loading
Loading
Loading
Loading
+31 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2025 The Android Open Source Project
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License
  -->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="@color/media_seamless_border">
    <item android:id="@android:id/background">
        <shape android:shape="rectangle">
            <stroke android:width="2dp" android:color="@color/media_seamless_border" />
            <solid android:color="#0000004D" />
            <corners android:radius="24dp"/>
            <padding
                android:left="8dp"
                android:right="8dp"
                android:top="4dp"
                android:bottom="4dp" />
        </shape>
    </item>
</ripple>
+422 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2025 The Android Open Source Project
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<!-- Layout for media session-based controls -->
<com.android.systemui.util.animation.TransitionLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/qs_media_controls"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:clipChildren="true"
    android:clipToPadding="true"
    android:gravity="center_horizontal|fill_vertical"
    android:forceHasOverlappingRendering="false"
    android:background="@drawable/qs_media_outline_layout_bg"
    android:clipToOutline="true"
    android:theme="@style/MediaPlayer">

    <ImageView
        android:id="@+id/album_art"
        android:layout_width="match_parent"
        android:layout_height="@dimen/qs_media_session_height_expanded"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:translationZ="0dp"
        android:scaleType="centerCrop"
        android:adjustViewBounds="true"
        android:clipToOutline="true"
        android:background="@drawable/qs_media_outline_album_bg"
        />

    <com.android.systemui.surfaceeffects.ripple.MultiRippleView
        android:id="@+id/touch_ripple_view"
        android:layout_width="match_parent"
        android:layout_height="@dimen/qs_media_session_height_expanded"
        app:layout_constraintStart_toStartOf="@id/album_art"
        app:layout_constraintEnd_toEndOf="@id/album_art"
        app:layout_constraintTop_toTopOf="@id/album_art"
        app:layout_constraintBottom_toBottomOf="@id/album_art"
        android:clipToOutline="true"
        android:background="@drawable/qs_media_outline_layout_bg"
        />

    <com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
        android:id="@+id/turbulence_noise_view"
        android:layout_width="match_parent"
        android:layout_height="@dimen/qs_media_session_height_expanded"
        app:layout_constraintStart_toStartOf="@id/album_art"
        app:layout_constraintEnd_toEndOf="@id/album_art"
        app:layout_constraintTop_toTopOf="@id/album_art"
        app:layout_constraintBottom_toBottomOf="@id/album_art"
        android:clipToOutline="true"
        android:background="@drawable/qs_media_outline_layout_bg"
        />

    <com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView
        android:id="@+id/loading_effect_view"
        android:layout_width="match_parent"
        android:layout_height="@dimen/qs_media_session_height_expanded"
        app:layout_constraintStart_toStartOf="@id/album_art"
        app:layout_constraintEnd_toEndOf="@id/album_art"
        app:layout_constraintTop_toTopOf="@id/album_art"
        app:layout_constraintBottom_toBottomOf="@id/album_art"
        android:clipToOutline="true"
        android:background="@drawable/qs_media_outline_layout_bg"
        />

    <!-- Guideline for output switcher -->
    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/seamless_vertical_guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.6" />

    <!-- Guideline for device suggestions -->
    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/device_suggestions_vertical_guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.3" />

    <!-- Guideline for action buttons (collapsed view only) -->
    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/action_button_guideline"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:orientation="vertical"
        app:layout_constraintGuide_end="@dimen/qs_media_session_collapsed_legacy_guideline" />

    <!-- App icon -->
    <com.android.internal.widget.CachingIconView
        android:id="@+id/icon"
        android:layout_width="@dimen/qs_media_app_icon_size"
        android:layout_height="@dimen/qs_media_app_icon_size"
        android:layout_marginStart="@dimen/qs_media_padding"
        android:layout_marginTop="@dimen/qs_media_padding"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


    <!-- Device Suggestions -->
    <LinearLayout
        android:id="@+id/device_suggestion_container"
        android:orientation="horizontal"
        android:paddingTop="@dimen/qs_media_padding"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:minHeight="@dimen/min_clickable_item_size"
        android:layout_marginStart="@dimen/qs_center_guideline_padding"
        app:layout_constraintEnd_toStartOf="@+id/media_seamless"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toEndOf="@id/device_suggestions_vertical_guideline"
        app:layout_constraintHorizontal_bias="1"
        app:layout_constrainedWidth="true"
        app:layout_constraintWidth_min="@dimen/min_clickable_item_size"
        app:layout_constraintHeight_min="@dimen/min_clickable_item_size">

        <LinearLayout
            android:id="@+id/device_suggestion_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:minHeight="@dimen/qs_seamless_height"
            android:maxHeight="@dimen/min_clickable_item_size"
            android:background="@drawable/qs_media_bordered_button"
            android:orientation="horizontal"
            android:visibility="gone"
            android:contentDescription="@string/quick_settings_media_device_label">

            <ImageView
                android:id="@+id/device_suggestion_icon"
                android:layout_width="@dimen/qs_seamless_icon_size"
                android:layout_height="@dimen/qs_seamless_icon_size"
                android:layout_gravity="center_vertical"
                android:src="@*android:drawable/ic_media_seamless" />
             <ProgressBar
                android:id="@+id/device_suggestion_progressbar"
                android:layout_width="@dimen/qs_seamless_icon_size"
                android:layout_height="@dimen/qs_seamless_icon_size"
                android:layout_gravity="center_vertical" />
            <TextView
                android:id="@+id/device_suggestion_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:layout_marginStart="4dp"
                android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
                android:singleLine="true"
                android:textDirection="locale"
                android:textSize="12sp"
                android:lineHeight="16sp" />
            </LinearLayout>
    </LinearLayout>


  <androidx.constraintlayout.widget.Barrier
    android:id="@+id/media_session_vertical_barrier"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:barrierDirection="end"
    app:constraint_referenced_ids="device_suggestion_container,seamless_vertical_guideline" />

    <!-- Seamless Output Switcher -->
    <LinearLayout
        android:id="@+id/media_seamless"
        android:orientation="horizontal"
        android:gravity="top|end"
        android:paddingTop="@dimen/qs_media_padding"
        android:paddingEnd="@dimen/qs_media_padding"
        android:background="@drawable/qs_media_light_source"
        android:forceHasOverlappingRendering="false"
        android:layout_width="wrap_content"
        android:minHeight="@dimen/min_clickable_item_size"
        android:layout_height="wrap_content"
        android:layout_marginStart="@dimen/qs_center_guideline_padding"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toEndOf="@id/media_session_vertical_barrier"
        app:layout_constraintHorizontal_bias="1"
        app:layout_constrainedWidth="true"
        app:layout_constraintWidth_min="@dimen/min_clickable_item_size"
        app:layout_constraintHeight_min="@dimen/min_clickable_item_size">
        <com.android.systemui.animation.view.LaunchableLinearLayout
            android:id="@+id/media_seamless_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:minHeight="@dimen/qs_seamless_height"
            android:maxHeight="@dimen/min_clickable_item_size"
            android:theme="@style/MediaPlayer.SolidButton"
            android:background="@drawable/qs_media_seamless_background"
            android:orientation="horizontal"
            android:contentDescription="@string/quick_settings_media_device_label">
            <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_gravity="center"
                android:tint="?android:attr/textColorPrimary"
                android:src="@*android:drawable/ic_media_seamless" />
            <TextView
                android:id="@+id/media_seamless_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:layout_marginStart="4dp"
                android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
                android:singleLine="true"
                android:text="@*android:string/ext_media_seamless_action"
                android:textDirection="locale"
                android:textSize="12sp"
                android:lineHeight="16sp" />
        </com.android.systemui.animation.view.LaunchableLinearLayout>
    </LinearLayout>

    <!-- Song name -->
    <TextView
        android:id="@+id/header_title"
        android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
        android:singleLine="true"
        android:textSize="16sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <!-- Explicit Indicator -->
    <com.android.internal.widget.CachingIconView
        android:id="@+id/media_explicit_indicator"
        android:layout_width="@dimen/qs_media_explicit_indicator_icon_size"
        android:layout_height="@dimen/qs_media_explicit_indicator_icon_size"
        android:src="@drawable/ic_media_explicit_indicator"
        />

    <!-- Artist name -->
    <TextView
        android:id="@+id/header_artist"
        android:fontFamily="@*android:string/config_headlineFontFamily"
        android:singleLine="true"
        style="@style/MediaPlayer.Subtitle"
        android:textSize="14sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <ImageButton
        android:id="@+id/actionPlayPause"
        style="@style/MediaPlayer.SessionAction.Primary"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_marginStart="@dimen/qs_media_padding"
        android:layout_marginEnd="@dimen/qs_media_padding" />

    <!-- See comment in media_session_collapsed.xml for how these barriers are used -->
    <androidx.constraintlayout.widget.Barrier
        android:id="@+id/media_action_barrier_start"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:orientation="vertical"
        app:layout_constraintTop_toBottomOf="@id/header_title"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:barrierDirection="start"
        app:constraint_referenced_ids="actionPrev,media_scrubbing_elapsed_time,media_progress_bar,actionNext,media_scrubbing_total_time,action0,action1,action2,action3,action4"
        />
    <androidx.constraintlayout.widget.Barrier
        android:id="@+id/media_action_barrier_end"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:orientation="vertical"
        app:layout_constraintTop_toBottomOf="@id/media_seamless"
        app:layout_constraintBottom_toBottomOf="parent"
        app:barrierDirection="end"
        app:constraint_referenced_ids="actionPrev,media_scrubbing_elapsed_time,media_progress_bar,actionNext,media_scrubbing_total_time,action0,action1,action2,action3,action4"
        app:layout_constraintRight_toRightOf="@id/actionPlayPause"
        />

    <!-- This barrier is used in expanded view to constrain the bottom row of actions -->
    <androidx.constraintlayout.widget.Barrier
        android:id="@+id/media_action_barrier_top"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:barrierDirection="top"
        app:constraint_referenced_ids="actionPrev,media_scrubbing_elapsed_time,media_progress_bar,actionNext,media_scrubbing_total_time,action0,action1,action2,action3,action4"
        />

    <!-- Button visibility will be controlled in code -->
    <ImageButton
        android:id="@+id/actionPrev"
        style="@style/MediaPlayer.SessionAction.Secondary"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_marginStart="4dp"
        android:layout_marginEnd="0dp"
        android:layout_marginBottom="@dimen/qs_media_padding"
        android:layout_marginTop="0dp"
        />

    <!-- Elapsed time, shown only when scrubbing -->
    <!-- The space to the left of the progress bar will either be actionPrev or
         media_scrubbing_elapsed_time, so they use the same layout constraints. Visibilities of
         elements are controlled in code. -->
    <TextView
        android:id="@+id/media_scrubbing_elapsed_time"
        style="@style/MediaPlayer.ScrubbingTime"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_marginStart="4dp"
        android:layout_marginEnd="0dp"
        android:layout_marginBottom="@dimen/qs_media_padding"
        android:layout_marginTop="0dp"
        android:visibility="gone"
        />

    <!-- Seek Bar -->
    <!-- As per Material Design on Bidirectionality, this is forced to LTR in code -->
    <SeekBar
        android:id="@+id/media_progress_bar"
        style="@style/MediaPlayer.ProgressBar"
        android:layout_width="0dp"
        android:layout_height="48dp"
        android:paddingTop="@dimen/qs_media_session_enabled_seekbar_vertical_padding"
        android:paddingBottom="12dp"
        android:maxHeight="@dimen/qs_media_enabled_seekbar_height"
        android:splitTrack="false"
        android:layout_marginBottom="@dimen/qs_media_padding"
        android:layout_marginTop="0dp"
        android:layout_marginStart="0dp"
        android:layout_marginEnd="0dp" />

    <ImageButton
        android:id="@+id/actionNext"
        style="@style/MediaPlayer.SessionAction.Secondary"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_marginStart="0dp"
        android:layout_marginEnd="@dimen/qs_media_action_spacing"
        android:layout_marginBottom="@dimen/qs_media_padding"
        android:layout_marginTop="0dp" />

    <!-- Total time, shown only when scrubbing -->
    <!-- The space to the right of the progress bar will either be actionNext or
         media_scrubbing_total_time, so they use the same layout constraints. Visibilities of
         elements are controlled in code. -->
    <TextView
        android:id="@+id/media_scrubbing_total_time"
        style="@style/MediaPlayer.ScrubbingTime"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_marginStart="0dp"
        android:layout_marginEnd="@dimen/qs_media_action_spacing"
        android:layout_marginBottom="@dimen/qs_media_padding"
        android:layout_marginTop="0dp"
        android:visibility="gone"
        />

    <ImageButton
        android:id="@+id/action0"
        style="@style/MediaPlayer.SessionAction.Secondary"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_marginStart="@dimen/qs_media_action_spacing"
        android:layout_marginEnd="@dimen/qs_media_action_spacing"
        android:layout_marginBottom="@dimen/qs_media_padding"
        android:layout_marginTop="0dp"/>

    <ImageButton
        android:id="@+id/action1"
        style="@style/MediaPlayer.SessionAction.Secondary"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_marginStart="@dimen/qs_media_action_spacing"
        android:layout_marginEnd="4dp"
        android:layout_marginBottom="@dimen/qs_media_padding"
        android:layout_marginTop="0dp" />

    <ImageButton
        android:id="@+id/action2"
        style="@style/MediaPlayer.SessionAction.Secondary"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_marginStart="@dimen/qs_media_action_spacing"
        android:layout_marginEnd="4dp"
        android:layout_marginBottom="@dimen/qs_media_padding"
        android:layout_marginTop="0dp" />

    <ImageButton
        android:id="@+id/action3"
        style="@style/MediaPlayer.SessionAction.Secondary"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_marginStart="@dimen/qs_media_action_spacing"
        android:layout_marginEnd="4dp"
        android:layout_marginBottom="@dimen/qs_media_padding"
        android:layout_marginTop="0dp" />

    <ImageButton
        android:id="@+id/action4"
        style="@style/MediaPlayer.SessionAction.Secondary"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_marginStart="@dimen/qs_media_action_spacing"
        android:layout_marginEnd="4dp"
        android:layout_marginBottom="@dimen/qs_media_padding"
        android:layout_marginTop="0dp" />

    <include
        layout="@layout/media_long_press_menu" />

</com.android.systemui.util.animation.TransitionLayout>
+6 −0
Original line number Diff line number Diff line
@@ -1167,6 +1167,12 @@
    <!-- Default name for the media device shown in the output switcher when the name is not available [CHAR LIMIT=30] -->
    <string name="media_seamless_other_device">Other device</string>

    <!-- Text on media control when there is a suggested device for playback. [CHAR LIMIT=30] -->
    <string name="media_suggestion_disconnected_text">Play on <xliff:g id = "device_name" example="Living room speaker">%s</xliff:g></string>

    <!-- Text on media control when connecting to a suggested device. [CHAR LIMIT=30] -->
    <string name="media_suggestion_failure_text">Can\'t connect. Try again.</string>

    <!-- QuickStep: Accessibility to toggle overview [CHAR LIMIT=40] -->
    <string name="quick_step_accessibility_toggle_overview">Toggle Overview</string>

+28 −6
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.graphics.drawable.RippleDrawable
import com.android.internal.R
import com.android.internal.annotations.VisibleForTesting
import com.android.media.flags.Flags.enableSuggestedDeviceApi
import com.android.settingslib.Utils
import com.android.systemui.Flags
import com.android.systemui.media.controls.ui.view.MediaViewHolder
@@ -144,6 +145,17 @@ internal constructor(
                it.effectColor = primaryColorList
            }
            mediaViewHolder.seekBar.progressBackgroundTintList = primaryColorList
            if (enableSuggestedDeviceApi()) {
                mediaViewHolder.deviceSuggestionText.setTextColor(primaryColor)
                mediaViewHolder.deviceSuggestionIcon.imageTintList = primaryColorList
                mediaViewHolder.deviceSuggestionConnectingIcon.indeterminateTintList =
                    primaryColorList
                mediaViewHolder.deviceSuggestionButton.backgroundTintList = primaryColorList
                (mediaViewHolder.deviceSuggestionButton.background as? RippleDrawable)?.let {
                    it.setColor(primaryColorList)
                    it.effectColor = primaryColorList
                }
            }
        }
    }

@@ -193,10 +205,16 @@ internal constructor(
                it.setColor(colorList)
                it.effectColor = colorList
            }
            if (enableSuggestedDeviceApi()) {
                (mediaViewHolder.deviceSuggestionButton.background as? RippleDrawable)?.let {
                    it.setColor(colorList)
                    it.effectColor = colorList
                }
            }
        }
    }

    private val colorSeamless: AnimatingColorTransition by lazy {
    private val colorSeamlessAndSuggested: AnimatingColorTransition by lazy {
        animatingColorTransitionFactory(
            loadDefaultColor(R.attr.textColorPrimary),
            { colorScheme: ColorScheme ->
@@ -208,9 +226,12 @@ internal constructor(
                    colorScheme.accent1.s100
                else colorScheme.accent1.s200
            },
            { seamlessColor: Int ->
                val accentColorList = ColorStateList.valueOf(seamlessColor)
            { seamlessAndSuggestedColor: Int ->
                val accentColorList = ColorStateList.valueOf(seamlessAndSuggestedColor)
                mediaViewHolder.seamlessButton.backgroundTintList = accentColorList
                if (enableSuggestedDeviceApi()) {
                    mediaViewHolder.deviceSuggestionButton.backgroundTintList = accentColorList
                }
            },
        )
    }
@@ -296,7 +317,7 @@ internal constructor(
        } else {
            arrayOf(
                surfaceColor,
                colorSeamless,
                colorSeamlessAndSuggested,
                accentPrimary,
                accentSecondary,
                textPrimary,
@@ -316,9 +337,10 @@ internal constructor(
        getColorTransitions().forEach {
            val isChanged = it.updateColorScheme(colorScheme)

            // Ignore changes to colorSeamless, since that is expected when toggling dark mode
            // Ignore changes to colorSeamlessAndSuggested, since that is expected when toggling
            // dark mode
            // TODO(media_controls_a11y_colors): remove, not necessary
            if (it == colorSeamless) return@forEach
            if (it == colorSeamlessAndSuggested) return@forEach

            anyChanged = isChanged || anyChanged
        }
+82 −0

File changed.

Preview size limit exceeded, changes collapsed.

Loading