Snap for 10453563 from 713d469f0193a991a8ce5acd0415692d34b9658d to mainline-conscrypt-release

Change-Id: Ie95445ef13259281b628a0c21f858c702cfd78b2
diff --git a/car-broadcastradio-support/OWNERS b/car-broadcastradio-support/OWNERS
index 01124ba..7d3fbc0 100644
--- a/car-broadcastradio-support/OWNERS
+++ b/car-broadcastradio-support/OWNERS
@@ -1,3 +1,6 @@
 # People who can approve changes for submission.
 # Automotive team
-twasilczyk@google.com
+xuweilin@google.com
+oscarazu@google.com
+ericjeong@google.com
+keunyoung@google.com
diff --git a/car-broadcastradio-support/src/com/android/car/broadcastradio/support/platform/ProgramInfoExt.java b/car-broadcastradio-support/src/com/android/car/broadcastradio/support/platform/ProgramInfoExt.java
index ce3d014..d377dbd 100644
--- a/car-broadcastradio-support/src/com/android/car/broadcastradio/support/platform/ProgramInfoExt.java
+++ b/car-broadcastradio-support/src/com/android/car/broadcastradio/support/platform/ProgramInfoExt.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.graphics.Bitmap;
 import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioManager.ProgramInfo;
 import android.hardware.radio.RadioMetadata;
 import android.media.MediaMetadata;
@@ -29,6 +30,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
 
 /**
  * Proposed extensions to android.hardware.radio.RadioManager.ProgramInfo.
@@ -71,13 +73,23 @@
     /**
      * Returns program name suitable to display.
      *
-     * If there is no program name, it falls back to channel name. Flags related to
+     * <p>If there is no program name, it falls back to channel name. Flags related to
      * the channel name display will be forwarded to the channel name generation method.
+     *
+     * @param info {@link ProgramInfo} to get name from
+     * @param flags Fallback method
+     * @param programNameOrder {@link RadioMetadata} metadata keys to pull from {@link ProgramInfo}
+     * for the program name
      */
-    public static @NonNull String getProgramName(@NonNull ProgramInfo info, @NameFlag int flags) {
+    @NonNull
+    public static String getProgramName(@NonNull ProgramInfo info, @NameFlag int flags,
+            @NonNull String[] programNameOrder) {
+        Objects.requireNonNull(info, "info can not be null.");
+        Objects.requireNonNull(programNameOrder, "programNameOrder can not be null");
+
         RadioMetadata meta = info.getMetadata();
         if (meta != null) {
-            for (String key : PROGRAM_NAME_ORDER) {
+            for (String key : programNameOrder) {
                 String value = meta.getString(key);
                 if (value != null) return value;
             }
@@ -104,6 +116,17 @@
     }
 
     /**
+     * Returns program name suitable to display.
+     *
+     * <p>If there is no program name, it falls back to channel name. Flags related to
+     * the channel name display will be forwarded to the channel name generation method.
+     */
+    @NonNull
+    public static String getProgramName(@NonNull ProgramInfo info, @NameFlag int flags) {
+        return getProgramName(info, flags, PROGRAM_NAME_ORDER);
+    }
+
+    /**
      * Proposed reimplementation of {@link RadioManager#ProgramInfo#getMetadata}.
      *
      * As opposed to the original implementation, it never returns null.
@@ -118,12 +141,69 @@
     }
 
     /**
-     * Converts {@ProgramInfo} to {@MediaMetadata}.
+     * Converts {@link ProgramInfo} to {@link MediaMetadata} for displaying.
      *
-     * This method is meant to be used for currently playing station in {@link MediaSession}.
+     * <p>This method is meant to be used for displaying the currently playing station in
+     *  {@link MediaSession}, only a subset of keys populated in {@link ProgramInfo#toMediaMetadata}
+     *  will be populated in this method.
+     *
+     * <ul>
+     * The following keys will be populated in the {@link MediaMetadata}:
+     *  <li>{@link MediaMetadata#METADATA_KEY_DISPLAY_TITLE}</li>
+     *  <li>{@link MediaMetadata#METADATA_KEY_DISPLAY_SUBTITLE}</li>
+     *  <li>{@link MediaMetadata#METADATA_KEY_ALBUM_ART}</li>
+     *  <li>{@link MediaMetadata#METADATA_KEY_USER_RATING}</li>
+     * <ul/>
      *
      * @param info {@link ProgramInfo} to convert
-     * @param isFavorite true, if a given program is a favorite
+     * @param isFavorite {@code true}, if a given program is a favorite
+     * @param imageResolver metadata images resolver/cache
+     * @param programNameOrder order of keys to look for program name in {@link ProgramInfo}
+     * @return {@link MediaMetadata} object
+     */
+    @NonNull
+    public static MediaMetadata toMediaDisplayMetadata(@NonNull ProgramInfo info,
+            boolean isFavorite, @NonNull ImageResolver imageResolver,
+            @NonNull String[] programNameOrder) {
+        Objects.requireNonNull(info, "info can not be null.");
+        Objects.requireNonNull(imageResolver, "imageResolver can not be null.");
+        Objects.requireNonNull(programNameOrder, "programNameOrder can not be null.");
+
+        MediaMetadata.Builder bld = new MediaMetadata.Builder();
+
+        ProgramSelector selector =
+                ProgramSelectorExt.createAmFmSelector(info.getLogicallyTunedTo().getValue());
+        String displayTitle = ProgramSelectorExt.getDisplayName(selector, info.getChannel());
+        bld.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, displayTitle);
+        String subtitle = getProgramName(info, /* flags= */ 0, programNameOrder);
+        bld.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, subtitle);
+
+        Bitmap bm = resolveAlbumArtBitmap(info.getMetadata(), imageResolver);
+        if (bm != null) bld.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bm);
+
+        bld.putRating(MediaMetadata.METADATA_KEY_USER_RATING, Rating.newHeartRating(isFavorite));
+
+        return bld.build();
+    }
+
+    /**
+     * Converts {@link ProgramInfo} to {@link MediaMetadata}.
+     *
+     * <p>This method is meant to be used for currently playing station in {@link MediaSession}.
+     *
+     * <ul>
+     * The following keys will be populated in the {@link MediaMetadata}:
+     *  <li>{@link MediaMetadata#METADATA_KEY_DISPLAY_TITLE}</li>
+     *  <li>{@link MediaMetadata#METADATA_KEY_TITLE}</li>
+     *  <li>{@link MediaMetadata#METADATA_KEY_ARTIST}</li>
+     *  <li>{@link MediaMetadata#METADATA_KEY_ALBUM}</li>
+     *  <li>{@link MediaMetadata#METADATA_KEY_DISPLAY_SUBTITLE}</li>
+     *  <li>{@link MediaMetadata#METADATA_KEY_ALBUM_ART}</li>
+     *  <li>{@link MediaMetadata#METADATA_KEY_USER_RATING}</li>
+     * <ul/>
+     *
+     * @param info {@link ProgramInfo} to convert
+     * @param isFavorite {@code true}, if a given program is a favorite
      * @param imageResolver metadata images resolver/cache
      * @return {@link MediaMetadata} object
      */
@@ -158,16 +238,22 @@
                 }
                 bld.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, subtitle);
             }
-            long albumArtId = RadioMetadataExt.getGlobalBitmapId(meta,
-                    RadioMetadata.METADATA_KEY_ART);
-            if (albumArtId != 0 && imageResolver != null) {
-                Bitmap bm = imageResolver.resolve(albumArtId);
-                if (bm != null) bld.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bm);
-            }
+
+            Bitmap bm = resolveAlbumArtBitmap(meta, imageResolver);
+            if (bm != null) bld.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bm);
         }
 
         bld.putRating(MediaMetadata.METADATA_KEY_USER_RATING, Rating.newHeartRating(isFavorite));
 
         return bld.build();
     }
+
+    private static Bitmap resolveAlbumArtBitmap(@NonNull RadioMetadata meta,
+            @NonNull ImageResolver imageResolver) {
+        long albumArtId = RadioMetadataExt.getGlobalBitmapId(meta, RadioMetadata.METADATA_KEY_ART);
+        if (albumArtId != 0 && imageResolver != null) {
+            return imageResolver.resolve(albumArtId);
+        }
+        return null;
+    }
 }
diff --git a/car-qc-lib/res/color/qc_seekbar_thumb_selector.xml b/car-qc-lib/res/color/qc_seekbar_thumb_selector.xml
new file mode 100644
index 0000000..bf94a61
--- /dev/null
+++ b/car-qc-lib/res/color/qc_seekbar_thumb_selector.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2023 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_enabled="false"
+        android:color="@color/qc_seekbar_thumb_disabled_on_dark"/>
+    <item android:color="@color/qc_seekbar_thumb"/>
+</selector>
diff --git a/car-qc-lib/res/color/qc_switch_thumb_selector.xml b/car-qc-lib/res/color/qc_switch_thumb_selector.xml
new file mode 100644
index 0000000..e0bfc22
--- /dev/null
+++ b/car-qc-lib/res/color/qc_switch_thumb_selector.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2023 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_enabled="false"
+        android:color="@color/qc_switch_thumb_color_disabled_on_dark"/>
+    <item android:color="@color/qc_switch_thumb_color"/>
+</selector>
diff --git a/car-qc-lib/res/drawable/qc_toggle_background.xml b/car-qc-lib/res/drawable/qc_toggle_background.xml
index 3688175..4e30cf4 100644
--- a/car-qc-lib/res/drawable/qc_toggle_background.xml
+++ b/car-qc-lib/res/drawable/qc_toggle_background.xml
@@ -16,8 +16,8 @@
   -->
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:id="@android:id/background"
-        android:width="@dimen/qc_toggle_size"
-        android:height="@dimen/qc_toggle_size"
+        android:width="@dimen/qc_toggle_background_size"
+        android:height="@dimen/qc_toggle_background_size"
         android:start="@dimen/qc_toggle_background_padding"
         android:top="@dimen/qc_toggle_background_padding"
         android:drawable="@drawable/qc_toggle_button_background">
diff --git a/car-qc-lib/res/drawable/qc_toggle_unavailable_background.xml b/car-qc-lib/res/drawable/qc_toggle_unavailable_background.xml
index e9f2e12..185b801 100644
--- a/car-qc-lib/res/drawable/qc_toggle_unavailable_background.xml
+++ b/car-qc-lib/res/drawable/qc_toggle_unavailable_background.xml
@@ -16,8 +16,8 @@
   -->
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:id="@android:id/background"
-        android:width="@dimen/qc_toggle_size"
-        android:height="@dimen/qc_toggle_size"
+        android:width="@dimen/qc_toggle_background_size"
+        android:height="@dimen/qc_toggle_background_size"
         android:start="@dimen/qc_toggle_background_padding"
         android:top="@dimen/qc_toggle_background_padding">
         <shape android:shape="rectangle">
diff --git a/car-qc-lib/res/layout/qc_row_view.xml b/car-qc-lib/res/layout/qc_row_view.xml
index 6656b29..a4af032 100644
--- a/car-qc-lib/res/layout/qc_row_view.xml
+++ b/car-qc-lib/res/layout/qc_row_view.xml
@@ -69,7 +69,6 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             app:barrierDirection="end"
-            app:constraint_referenced_ids="qc_icon"
             app:barrierAllowsGoneWidgets="false"/>
 
         <com.android.car.ui.uxr.DrawableStateTextView
@@ -101,8 +100,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             app:barrierDirection="top"
-            app:constraint_referenced_ids="qc_seekbar_wrapper"
-            app:barrierAllowsGoneWidgets="false"/>
+            app:constraint_referenced_ids="qc_seekbar_wrapper"/>
 
         <androidx.preference.UnPressableLinearLayout
             android:id="@+id/qc_seekbar_wrapper"
@@ -121,7 +119,7 @@
             app:layout_constraintTop_toBottomOf="@+id/barrier2"
             app:layout_constraintBottom_toBottomOf="parent">
             <com.android.car.qc.view.QCSeekBarView
-                android:id="@+id/seekbar"
+                android:id="@+id/qc_seekbar"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 style="@style/Widget.QC.SeekBar"/>
diff --git a/car-qc-lib/res/values/colors.xml b/car-qc-lib/res/values/colors.xml
index 62bcfdc..a2b85ff 100644
--- a/car-qc-lib/res/values/colors.xml
+++ b/car-qc-lib/res/values/colors.xml
@@ -20,4 +20,12 @@
     <color name="qc_toggle_unavailable_background_color">@android:color/transparent</color>
     <color name="qc_toggle_unavailable_color">#37FFFFFF</color>
     <color name="qc_toggle_rotary_shadow_color">#C7000000</color>
-</resources>
\ No newline at end of file
+    <!-- The SeekBar thumb color. -->
+    <color name="qc_seekbar_thumb">#FFFFFF</color>
+    <!-- The SeekBar thumb color when disabled. Use for the dark theme. -->
+    <color name="qc_seekbar_thumb_disabled_on_dark">#757575</color>
+    <!-- The Switch thumb color. -->
+    <color name="qc_switch_thumb_color">#FFFFFF</color>
+    <!-- The Switch thumb color when disabled. Use for the dark theme. -->
+    <color name="qc_switch_thumb_color_disabled_on_dark">#757575</color>
+</resources>
diff --git a/car-qc-lib/res/values/dimens.xml b/car-qc-lib/res/values/dimens.xml
index b973774..912891f 100644
--- a/car-qc-lib/res/values/dimens.xml
+++ b/car-qc-lib/res/values/dimens.xml
@@ -24,7 +24,8 @@
     <dimen name="qc_row_content_margin">16dp</dimen>
 
     <dimen name="qc_action_items_horizontal_margin">32dp</dimen>
-    <dimen name="qc_toggle_size">72dp</dimen>
+    <dimen name="qc_toggle_size">80dp</dimen>
+    <dimen name="qc_toggle_background_size">72dp</dimen>
     <dimen name="qc_toggle_margin">12dp</dimen>
     <dimen name="qc_row_horizontal_margin">16dp</dimen>
     <dimen name="qc_toggle_background_radius">16dp</dimen>
diff --git a/car-qc-lib/res/values/overlayable.xml b/car-qc-lib/res/values/overlayable.xml
new file mode 100644
index 0000000..d5b405a
--- /dev/null
+++ b/car-qc-lib/res/values/overlayable.xml
@@ -0,0 +1,87 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<!--
+THIS FILE WAS AUTO GENERATED, DO NOT EDIT MANUALLY.
+REGENERATE USING packages/apps/Car/tests/tools/rro/generate-overlayable.py
+-->
+<resources>
+    <overlayable name="car-qc-lib">
+        <policy type="system|product|signature">
+            <item type="attr" name="state_toggle_unavailable"/>
+            <item type="color" name="qc_start_icon_color"/>
+            <item type="color" name="qc_toggle_background_color"/>
+            <item type="color" name="qc_toggle_icon_fill_color"/>
+            <item type="color" name="qc_toggle_off_background_color"/>
+            <item type="color" name="qc_toggle_rotary_shadow_color"/>
+            <item type="color" name="qc_toggle_unavailable_background_color"/>
+            <item type="color" name="qc_toggle_unavailable_color"/>
+            <item type="dimen" name="qc_action_items_horizontal_margin"/>
+            <item type="dimen" name="qc_row_content_margin"/>
+            <item type="dimen" name="qc_row_horizontal_margin"/>
+            <item type="dimen" name="qc_row_icon_margin_end"/>
+            <item type="dimen" name="qc_row_icon_size"/>
+            <item type="dimen" name="qc_row_margin_vertical"/>
+            <item type="dimen" name="qc_row_min_height"/>
+            <item type="dimen" name="qc_row_padding_end"/>
+            <item type="dimen" name="qc_row_padding_start"/>
+            <item type="dimen" name="qc_seekbar_padding_top"/>
+            <item type="dimen" name="qc_toggle_background_padding"/>
+            <item type="dimen" name="qc_toggle_background_radius"/>
+            <item type="dimen" name="qc_toggle_foreground_icon_inset"/>
+            <item type="dimen" name="qc_toggle_margin"/>
+            <item type="dimen" name="qc_toggle_rotary_highlight_radius"/>
+            <item type="dimen" name="qc_toggle_rotary_highlight_size"/>
+            <item type="dimen" name="qc_toggle_rotary_shadow_padding"/>
+            <item type="dimen" name="qc_toggle_rotary_shadow_radius"/>
+            <item type="dimen" name="qc_toggle_rotary_shadow_size"/>
+            <item type="dimen" name="qc_toggle_rotary_shadow_width"/>
+            <item type="dimen" name="qc_toggle_background_size"/>
+            <item type="dimen" name="qc_toggle_size"/>
+            <item type="dimen" name="qc_toggle_unavailable_outline_width"/>
+            <item type="drawable" name="qc_row_action_divider"/>
+            <item type="drawable" name="qc_seekbar_wrapper_background"/>
+            <item type="drawable" name="qc_toggle_background"/>
+            <item type="drawable" name="qc_toggle_button_background"/>
+            <item type="drawable" name="qc_toggle_rotary_background"/>
+            <item type="drawable" name="qc_toggle_rotary_highlight"/>
+            <item type="drawable" name="qc_toggle_rotary_shadow"/>
+            <item type="drawable" name="qc_toggle_unavailable_background"/>
+            <item type="id" name="barrier1"/>
+            <item type="id" name="barrier2"/>
+            <item type="id" name="qc_icon"/>
+            <item type="id" name="qc_row_content"/>
+            <item type="id" name="qc_row_end_items"/>
+            <item type="id" name="qc_row_start_items"/>
+            <item type="id" name="qc_seekbar"/>
+            <item type="id" name="qc_seekbar_wrapper"/>
+            <item type="id" name="qc_summary"/>
+            <item type="id" name="qc_tile_toggle_button"/>
+            <item type="id" name="qc_tile_wrapper"/>
+            <item type="id" name="qc_title"/>
+            <item type="id" name="qc_toggle_button"/>
+            <item type="layout" name="qc_action_switch"/>
+            <item type="layout" name="qc_action_toggle"/>
+            <item type="layout" name="qc_row_view"/>
+            <item type="layout" name="qc_tile_view"/>
+            <item type="style" name="TextAppearance.QC"/>
+            <item type="style" name="TextAppearance.QC.Subtitle"/>
+            <item type="style" name="TextAppearance.QC.Title"/>
+            <item type="style" name="Widget.QC"/>
+            <item type="style" name="Widget.QC.SeekBar"/>
+        </policy>
+    </overlayable>
+</resources>
\ No newline at end of file
diff --git a/car-qc-lib/src/com/android/car/qc/QCActionItem.java b/car-qc-lib/src/com/android/car/qc/QCActionItem.java
index c476e09..b7b9cd1 100644
--- a/car-qc-lib/src/com/android/car/qc/QCActionItem.java
+++ b/car-qc-lib/src/com/android/car/qc/QCActionItem.java
@@ -17,11 +17,13 @@
 package com.android.car.qc;
 
 import android.app.PendingIntent;
+import android.content.Context;
 import android.graphics.drawable.Icon;
 import android.os.Parcel;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
 
 /**
  * Quick Control Action that are includes as either start or end actions in {@link QCRow}
@@ -32,14 +34,17 @@
     private Icon mIcon;
     private PendingIntent mAction;
     private PendingIntent mDisabledClickAction;
+    private String mContentDescription;
 
     public QCActionItem(@NonNull @QCItemType String type, boolean isChecked, boolean isEnabled,
             boolean isAvailable, boolean isClickableWhileDisabled, @Nullable Icon icon,
-            @Nullable PendingIntent action, @Nullable PendingIntent disabledClickAction) {
+            @Nullable String contentDescription, @Nullable PendingIntent action,
+            @Nullable PendingIntent disabledClickAction) {
         super(type, isEnabled, isClickableWhileDisabled);
         mIsChecked = isChecked;
         mIsAvailable = isAvailable;
         mIcon = icon;
+        mContentDescription = contentDescription;
         mAction = action;
         mDisabledClickAction = disabledClickAction;
     }
@@ -52,6 +57,10 @@
         if (hasIcon) {
             mIcon = Icon.CREATOR.createFromParcel(in);
         }
+        boolean hasContentDescription = in.readBoolean();
+        if (hasContentDescription) {
+            mContentDescription = in.readString();
+        }
         boolean hasAction = in.readBoolean();
         if (hasAction) {
             mAction = PendingIntent.CREATOR.createFromParcel(in);
@@ -72,6 +81,11 @@
         if (includeIcon) {
             mIcon.writeToParcel(dest, flags);
         }
+        boolean hasContentDescription = mContentDescription != null;
+        dest.writeBoolean(hasContentDescription);
+        if (hasContentDescription) {
+            dest.writeString(mContentDescription);
+        }
         boolean hasAction = mAction != null;
         dest.writeBoolean(hasAction);
         if (hasAction) {
@@ -107,6 +121,11 @@
         return mIcon;
     }
 
+    @Nullable
+    public String getContentDescription() {
+        return mContentDescription;
+    }
+
     public static Creator<QCActionItem> CREATOR = new Creator<QCActionItem>() {
         @Override
         public QCActionItem createFromParcel(Parcel source) {
@@ -131,6 +150,7 @@
         private Icon mIcon;
         private PendingIntent mAction;
         private PendingIntent mDisabledClickAction;
+        private String mContentDescription;
 
         public Builder(@NonNull @QCItemType String type) {
             if (!isValidType(type)) {
@@ -180,6 +200,23 @@
         }
 
         /**
+         * Sets the content description
+         */
+        public Builder setContentDescription(@Nullable String contentDescription) {
+            mContentDescription = contentDescription;
+            return this;
+        }
+
+        /**
+         * Sets the string resource to use for content description
+         */
+        public Builder setContentDescription(@NonNull Context context,
+                @StringRes int contentDescriptionResId) {
+            mContentDescription = context.getString(contentDescriptionResId);
+            return this;
+        }
+
+        /**
          * Sets the PendingIntent to be sent when the action item is clicked.
          */
         public Builder setAction(@Nullable PendingIntent action) {
@@ -200,7 +237,8 @@
          */
         public QCActionItem build() {
             return new QCActionItem(mType, mIsChecked, mIsEnabled, mIsAvailable,
-                    mIsClickableWhileDisabled, mIcon, mAction, mDisabledClickAction);
+                    mIsClickableWhileDisabled, mIcon, mContentDescription, mAction,
+                    mDisabledClickAction);
         }
 
         private boolean isValidType(String type) {
diff --git a/car-qc-lib/src/com/android/car/qc/provider/BaseQCProvider.java b/car-qc-lib/src/com/android/car/qc/provider/BaseQCProvider.java
index 61db361..8e9e550 100644
--- a/car-qc-lib/src/com/android/car/qc/provider/BaseQCProvider.java
+++ b/car-qc-lib/src/com/android/car/qc/provider/BaseQCProvider.java
@@ -172,7 +172,11 @@
         try {
             StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                     .detectAll()
-                    .penaltyDeath()
+
+                    // TODO(268275789): Revert back to penaltyDeath and ensure it works in
+                    //                  presubmit
+                    .penaltyLog()
+
                     .build());
             return onBind(uri);
         } finally {
diff --git a/car-qc-lib/src/com/android/car/qc/view/QCRowView.java b/car-qc-lib/src/com/android/car/qc/view/QCRowView.java
index 1e10e4b..2b6b7a5 100644
--- a/car-qc-lib/src/com/android/car/qc/view/QCRowView.java
+++ b/car-qc-lib/src/com/android/car/qc/view/QCRowView.java
@@ -193,7 +193,7 @@
         mStartItemsContainer = findViewById(R.id.qc_row_start_items);
         mEndItemsContainer = findViewById(R.id.qc_row_end_items);
         mSeekBarContainer = findViewById(R.id.qc_seekbar_wrapper);
-        mSeekBar = findViewById(R.id.seekbar);
+        mSeekBar = findViewById(R.id.qc_seekbar);
     }
 
     void setActionListener(QCActionListener listener) {
@@ -311,7 +311,10 @@
                 (action.isEnabled() || action.isClickableWhileDisabled()) && action.isAvailable();
         switchView.setOnCheckedChangeListener(null);
         switchView.setEnabled(shouldEnableView);
+        switchView.setThumbTintList(getContext().getColorStateList(
+                R.color.qc_switch_thumb_selector));
         switchView.setChecked(action.isChecked());
+        switchView.setContentDescription(action.getContentDescription());
         switchView.setOnTouchListener((v, event) -> {
             if (!action.isEnabled()) {
                 if (event.getActionMasked() == MotionEvent.ACTION_UP) {
@@ -345,6 +348,7 @@
         toggleButton.setOnCheckedChangeListener(null);
         Drawable icon = QCViewUtils.getInstance(mContext).getToggleIcon(
                 action.getIcon(), action.isAvailable());
+        toggleButton.setContentDescription(action.getContentDescription());
         toggleButton.setButtonDrawable(icon);
         toggleButton.setChecked(action.isChecked());
         toggleButton.setEnabled(shouldEnableView);
@@ -397,13 +401,15 @@
             // remove current action view
             root.removeView(actionView);
         }
-        actionView = mLayoutInflater.inflate(resId, /* root= */ null);
+        actionView = mLayoutInflater.inflate(resId, root, /* attachToRoot= */ false);
         root.addView(actionView);
         return actionView;
     }
 
     private void initSlider(QCSlider slider) {
         mQCSlider = slider;
+        CarUiUtils.makeAllViewsEnabled(mSeekBar, slider.isEnabled());
+
         mSeekBar.setOnSeekBarChangeListener(null);
         mSeekBar.setMin(slider.getMin());
         mSeekBar.setMax(slider.getMax());
@@ -411,6 +417,8 @@
         mSeekBar.setEnabled(slider.isEnabled());
         mSeekBar.setClickableWhileDisabled(slider.isClickableWhileDisabled());
         mSeekBar.setDisabledClickListener(seekBar -> fireAction(slider, new Intent()));
+        mSeekBar.setThumbTintList(getContext().getColorStateList(
+                R.color.qc_seekbar_thumb_selector));
         if (!slider.isEnabled() && mInDirectManipulationMode) {
             setInDirectManipulationMode(mSeekBarContainer, mSeekBar, false);
         }
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/QCActionItemTest.java b/car-qc-lib/tests/unit/src/com/android/car/qc/QCActionItemTest.java
index ff70540..c945301 100644
--- a/car-qc-lib/tests/unit/src/com/android/car/qc/QCActionItemTest.java
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/QCActionItemTest.java
@@ -33,32 +33,34 @@
 
 @RunWith(AndroidJUnit4.class)
 public class QCActionItemTest extends QCItemTestCase<QCActionItem> {
+    private static final String TEST_CONTENT_DESCRIPTION = "test_content_description";
 
     @Test
     public void onCreate_invalidType_throwsException() {
         assertThrows(IllegalArgumentException.class,
                 () -> createAction("INVALID_TYPE", /* action= */ null,
-                        /* disabledAction= */ null, /* icon= */ null));
+                        /* disabledAction= */ null, /* icon= */ null, /* contentDescription=*/
+                        null));
     }
 
     @Test
     public void onCreateSwitch_hasCorrectType() {
         QCActionItem action = createAction(QC_TYPE_ACTION_SWITCH, /* action= */ null,
-                /* disabledAction= */ null, /* icon= */null);
+                /* disabledAction= */ null, /* icon= */null, /* contentDescription=*/ null);
         assertThat(action.getType()).isEqualTo(QC_TYPE_ACTION_SWITCH);
     }
 
     @Test
     public void onCreateToggle_hasCorrectType() {
         QCActionItem action = createAction(QC_TYPE_ACTION_TOGGLE, /* action= */ null,
-                /* disabledAction= */ null, /* icon= */ null);
+                /* disabledAction= */ null, /* icon= */ null, /* contentDescription=*/ null);
         assertThat(action.getType()).isEqualTo(QC_TYPE_ACTION_TOGGLE);
     }
 
     @Test
     public void onBundle_nullActions_noCrash() {
         QCActionItem action = createAction(QC_TYPE_ACTION_TOGGLE, /* action= */ null,
-                /* disabledAction= */ null, mDefaultIcon);
+                /* disabledAction= */ null, mDefaultIcon, /* contentDescription=*/ null);
         writeAndLoadFromBundle(action);
         // Test passes if this doesn't crash
     }
@@ -66,7 +68,7 @@
     @Test
     public void onBundle_nullIcon_noCrash() {
         QCActionItem action = createAction(QC_TYPE_ACTION_TOGGLE, mDefaultAction,
-                mDefaultDisabledAction, /* icon= */ null);
+                mDefaultDisabledAction, /* icon= */ null, /* contentDescription=*/ null);
         writeAndLoadFromBundle(action);
         // Test passes if this doesn't crash
     }
@@ -74,7 +76,7 @@
     @Test
     public void onBundle_switch_accurateData() {
         QCActionItem action = createAction(QC_TYPE_ACTION_SWITCH, mDefaultAction,
-                mDefaultDisabledAction, /* icon= */ null);
+                mDefaultDisabledAction, /* icon= */ null, TEST_CONTENT_DESCRIPTION);
         QCActionItem newAction = writeAndLoadFromBundle(action);
         assertThat(newAction.getType()).isEqualTo(QC_TYPE_ACTION_SWITCH);
         assertThat(newAction.isChecked()).isTrue();
@@ -82,12 +84,13 @@
         assertThat(newAction.isClickableWhileDisabled()).isFalse();
         assertThat(newAction.getPrimaryAction()).isNotNull();
         assertThat(newAction.getIcon()).isNull();
+        assertThat(newAction.getContentDescription()).isEqualTo(TEST_CONTENT_DESCRIPTION);
     }
 
     @Test
     public void onBundle_toggle_accurateDate() {
         QCActionItem action = createAction(QC_TYPE_ACTION_TOGGLE, mDefaultAction,
-                mDefaultDisabledAction, mDefaultIcon);
+                mDefaultDisabledAction, mDefaultIcon, TEST_CONTENT_DESCRIPTION);
         QCActionItem newAction = writeAndLoadFromBundle(action);
         assertThat(newAction.getType()).isEqualTo(QC_TYPE_ACTION_TOGGLE);
         assertThat(newAction.isChecked()).isTrue();
@@ -95,16 +98,18 @@
         assertThat(newAction.isClickableWhileDisabled()).isFalse();
         assertThat(newAction.getPrimaryAction()).isNotNull();
         assertThat(newAction.getIcon()).isNotNull();
+        assertThat(newAction.getContentDescription()).isEqualTo(TEST_CONTENT_DESCRIPTION);
     }
 
     private QCActionItem createAction(String type, PendingIntent action,
-            PendingIntent disabledAction, Icon icon) {
+            PendingIntent disabledAction, Icon icon, String contentDescription) {
         return new QCActionItem.Builder(type)
                 .setChecked(true)
                 .setEnabled(true)
                 .setAction(action)
                 .setDisabledClickAction(disabledAction)
                 .setIcon(icon)
+                .setContentDescription(contentDescription)
                 .build();
     }
 }
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCRowViewTest.java b/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCRowViewTest.java
index 647317a..9c1aa79 100644
--- a/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCRowViewTest.java
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCRowViewTest.java
@@ -30,12 +30,14 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.ColorStateList;
 import android.graphics.drawable.Icon;
 import android.view.MotionEvent;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.SeekBar;
+import android.widget.Switch;
 import android.widget.TextView;
 
 import androidx.test.annotation.UiThreadTest;
@@ -203,11 +205,50 @@
                 .addSlider(new QCSlider.Builder().setInputAction(action).build())
                 .build();
         mView.setRow(row);
-        SeekBar seekBar = mView.findViewById(R.id.seekbar);
+        SeekBar seekBar = mView.findViewById(R.id.qc_seekbar);
         seekBar.setProgress(50);
         MotionEvent motionEvent = ExtendedMockito.mock(MotionEvent.class);
         ExtendedMockito.when(motionEvent.getAction()).thenReturn(MotionEvent.ACTION_UP);
         seekBar.onTouchEvent(motionEvent);
         verify(action).send(any(Context.class), anyInt(), any(Intent.class));
     }
+
+    @Test
+    @UiThreadTest
+    public void setRow_switchViewThumbTintList() {
+        PendingIntent action = mock(PendingIntent.class);
+        QCRow row = new QCRow.Builder()
+                .addEndItem(
+                        new QCActionItem.Builder(QC_TYPE_ACTION_SWITCH).setAction(action).build())
+                .build();
+        mView.setRow(row);
+        LinearLayout endContainer = mView.findViewById(R.id.qc_row_end_items);
+        assertThat(endContainer.getChildCount()).isEqualTo(1);
+        Switch switchView = (Switch) endContainer.getChildAt(0);
+        assertThat(switchView.getThumbTintList()).isNotNull();
+
+        ColorStateList switchColorStateList = switchView.getThumbTintList();
+        int[] enabledState = {android.R.attr.state_enabled};
+        int[] disabledState = {-android.R.attr.state_enabled};
+        assertThat(switchColorStateList.getColorForState(enabledState, 0)).isNotEqualTo(
+                switchColorStateList.getColorForState(disabledState, 0));
+    }
+
+    @Test
+    @UiThreadTest
+    public void setRow_sliderViewThumbTintList() {
+        PendingIntent action = mock(PendingIntent.class);
+        QCRow row = new QCRow.Builder()
+                .addSlider(new QCSlider.Builder().setInputAction(action).build())
+                .build();
+        mView.setRow(row);
+        SeekBar seekBar = mView.findViewById(R.id.qc_seekbar);
+        assertThat(seekBar.getThumbTintList()).isNotNull();
+
+        ColorStateList seekBarColorStateList = seekBar.getThumbTintList();
+        int[] enabledState = {android.R.attr.state_enabled};
+        int[] disabledState = {-android.R.attr.state_enabled};
+        assertThat(seekBarColorStateList.getColorForState(enabledState, 0)).isNotEqualTo(
+                seekBarColorStateList.getColorForState(disabledState, 0));
+    }
 }
diff --git a/tools/rro/generate-overlayable.py b/tools/rro/generate-overlayable.py
index 2f76a2d..b814118 100755
--- a/tools/rro/generate-overlayable.py
+++ b/tools/rro/generate-overlayable.py
@@ -49,10 +49,12 @@
     optional_args.add_argument('-o', '--outputFile', default='', help='Output file path (absolute or relative to cwd). If empty, output to stdout')
     required_args = parser.add_argument_group('required arguments')
     required_args.add_argument('-n', '--targetName', help='Overlayable name for the overlay.', required=True)
-    required_args.add_argument('-r', '--resourcePath', help='Path to resource directory (absolute or relative to cwd)', required=True)
+    required_args.add_argument('-r', '--resourcePath', help='Path to resource directory (absolute or relative to cwd)', required=True, action='append')
     args = parser.parse_args()
 
-    resources = get_all_resources(args.resourcePath, args.excludeFiles)
+    resources = set()
+    for path in args.resourcePath:
+        resources |= get_all_resources(path, args.excludeFiles)
     generate_overlayable_file(resources, args.targetName, args.policyType, args.outputFile)
 
 def generate_overlayable_file(resources, target_name, policy_type, output_file):
diff --git a/tools/rro/resource_utils.py b/tools/rro/resource_utils.py
index 5f35e1a..fe4c52c 100644
--- a/tools/rro/resource_utils.py
+++ b/tools/rro/resource_utils.py
@@ -15,7 +15,11 @@
 
 import os
 import re
-import lxml.etree as etree
+try:
+    import lxml.etree as etree
+except ImportError:
+    print("Please install 'lxml' python package and retry.")
+    sys.exit(1)
 
 class ResourceLocation:
     def __init__(self, file, line=None):
diff --git a/tools/rro/verify-overlayable.py b/tools/rro/verify-overlayable.py
index 05feb51..ef99aa4 100755
--- a/tools/rro/verify-overlayable.py
+++ b/tools/rro/verify-overlayable.py
@@ -28,16 +28,19 @@
     parser = argparse.ArgumentParser(description='Verify overlayable.xml.')
     optional_args = parser.add_argument_group('optional arguments')
     optional_args.add_argument('-e', '--excludeFiles', nargs='*', help='File paths (absolute or relative to cwd) that should be excluded when generating overlayable.xml')
+    optional_args.add_argument('-m', '--errorMessage', nargs='*', help='Custom error message if resources are added or removed.')
     required_args = parser.add_argument_group('required arguments')
-    required_args.add_argument('-r', '--resourcePath', help='Path to resource directory (absolute or relative to cwd)', required=True)
+    required_args.add_argument('-r', '--resourcePath', help='Path to resource directory (absolute or relative to cwd)', required=True, action='append')
     required_args.add_argument('-o', '--overlayableFilePath', help='Filepath to overlayable.xml (absolute or relative to cwd).', required=True)
     args = parser.parse_args()
 
-    resources = get_all_resources(args.resourcePath, args.excludeFiles)
+    resources = set()
+    for path in args.resourcePath:
+        resources |= get_all_resources(path, args.excludeFiles)
     old_mapping = get_resources_from_single_file(args.overlayableFilePath)
-    compare_resources(old_mapping, resources, args.overlayableFilePath)
+    compare_resources(old_mapping, resources, args.overlayableFilePath, args.errorMessage)
 
-def compare_resources(old_mapping, new_mapping, res_public_file):
+def compare_resources(old_mapping, new_mapping, res_public_file, errorMsg):
     removed = old_mapping.difference(new_mapping)
     added = new_mapping.difference(old_mapping)
     if len(removed) > 0:
@@ -45,10 +48,13 @@
     if len(added) > 0:
         print('Resources added:\n' + '\n'.join(map(lambda x: str(x), added)))
     if len(added) + len(removed) > 0:
-        print("Some resource have been modified. If this is intentional please " +
-              "run 'python3 generate-overlayable.py' again and submit the new %s" % res_public_file)
-        print("Some projects may include $PROJECT_TOP/tools/generate-overlayable.sh which calls " +
-              "the above command with the appropriate command line arguments")
+        if errorMsg is not None:
+            print(errorMsg)
+        else:
+            print("Some resource have been modified. If this is intentional please " +
+                  "run 'python3 generate-overlayable.py' again and submit the new %s" % res_public_file)
+            print("Some projects may include $PROJECT_TOP/tools/generate-overlayable.sh which calls " +
+                  "the above command with the appropriate command line arguments")
         sys.exit(1)
 
 if __name__ == '__main__':