Merge tag 'android-13.0.0_r52' into int/13/fp3

Android 13.0.0 Release 52 (TQ3A.230605.012)

* tag 'android-13.0.0_r52':
  QuickControls UI issues in RTL
  Add toMediaMetadata and getProgramName that take list of program name keys as paramter
  Support passing multiple res folders

Change-Id: I3fd9d424dd3b25d9d192751e8e9f7ffee3f34e9b
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/layout/qc_row_view.xml b/car-qc-lib/res/layout/qc_row_view.xml
index be8c391..2d95888 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
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/verify-overlayable.py b/tools/rro/verify-overlayable.py
index 2625310..ef99aa4 100755
--- a/tools/rro/verify-overlayable.py
+++ b/tools/rro/verify-overlayable.py
@@ -30,11 +30,13 @@
     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, args.errorMessage)