Update owner file for car radio system lib am: 8611d37c22

Original change: https://android-review.googlesource.com/c/platform/packages/apps/Car/systemlibs/+/2948661

Change-Id: I18adf160baa5c268d56ba4a6c00eb71f9afd156b
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.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 d377dbd..c96521a 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
@@ -30,6 +30,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Comparator;
 import java.util.Objects;
 
 /**
@@ -171,8 +172,14 @@
 
         MediaMetadata.Builder bld = new MediaMetadata.Builder();
 
-        ProgramSelector selector =
-                ProgramSelectorExt.createAmFmSelector(info.getLogicallyTunedTo().getValue());
+        ProgramSelector selector;
+        ProgramSelector.Identifier logicallyTunedTo = info.getLogicallyTunedTo();
+        if (logicallyTunedTo != null && logicallyTunedTo.getType()
+                == ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY) {
+            selector = ProgramSelectorExt.createAmFmSelector(logicallyTunedTo.getValue());
+        } else {
+            selector = info.getSelector();
+        }
         String displayTitle = ProgramSelectorExt.getDisplayName(selector, info.getChannel());
         bld.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, displayTitle);
         String subtitle = getProgramName(info, /* flags= */ 0, programNameOrder);
@@ -256,4 +263,12 @@
         }
         return null;
     }
+    public static class ProgramInfoComparator implements Comparator<RadioManager.ProgramInfo> {
+        @Override
+        public int compare(RadioManager.ProgramInfo info1, RadioManager.ProgramInfo info2) {
+            Comparator<ProgramSelector> selectorComparator =
+                    new ProgramSelectorExt.ProgramSelectorComparator();
+            return selectorComparator.compare(info1.getSelector(), info2.getSelector());
+        }
+    }
 }
diff --git a/car-broadcastradio-support/src/com/android/car/broadcastradio/support/platform/ProgramSelectorExt.java b/car-broadcastradio-support/src/com/android/car/broadcastradio/support/platform/ProgramSelectorExt.java
index 4b3583b..555176d 100644
--- a/car-broadcastradio-support/src/com/android/car/broadcastradio/support/platform/ProgramSelectorExt.java
+++ b/car-broadcastradio-support/src/com/android/car/broadcastradio/support/platform/ProgramSelectorExt.java
@@ -29,6 +29,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.text.DecimalFormat;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -81,6 +82,11 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface NameFlag {}
 
+    /**
+     * Invalid value for a {@link ProgramSelector.Identifier}
+     */
+    public static int INVALID_IDENTIFIER_VALUE = 0;
+
     private static final String URI_SCHEME_BROADCASTRADIO = "broadcastradio";
     private static final String URI_AUTHORITY_PROGRAM = "program";
     private static final String URI_VENDOR_PREFIX = "VENDOR_";
@@ -251,26 +257,81 @@
     }
 
     /**
+     * Get frequency from a {@link ProgramSelector}.
+     *
+     * @param selector Program selector
+     * @return frequency of the first {@link ProgramSelector#IDENTIFIER_TYPE_AMFM_FREQUENCY} id in
+     * program selector if it exists, otherwise the AM/FM frequency in
+     * {@link ProgramSelector#IDENTIFIER_TYPE_HD_STATION_ID_EXT} id if it is the primary id,
+     * {@link #INVALID_IDENTIFIER_VALUE} otherwise.
+     */
+    public static int getFrequency(@NonNull ProgramSelector selector) {
+        if (ProgramSelectorExt.hasId(selector, ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY)) {
+            return (int) selector.getFirstId(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY);
+        } else if (selector.getPrimaryId().getType()
+                == ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT) {
+            return IdentifierExt.asHdPrimary(selector.getPrimaryId()).getFrequency();
+        } else if (selector.getPrimaryId().getType()
+                == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT
+                || selector.getPrimaryId().getType()
+                == ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT) {
+            try {
+                return (int) selector.getFirstId(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY);
+            } catch (IllegalArgumentException e) {
+                return INVALID_IDENTIFIER_VALUE;
+            }
+        }
+        return INVALID_IDENTIFIER_VALUE;
+    }
+
+    /**
+     * Get ensemble value from a DAB-type {@link ProgramSelector}.
+     *
+     * @param selector Program selector
+     * @return Value of the first {@link ProgramSelector#IDENTIFIER_TYPE_DAB_ENSEMBLE} identifier,
+     * 0 otherwise
+     */
+    public static int getDabEnsemble(@NonNull ProgramSelector selector) {
+        try {
+            return (int) selector.getFirstId(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE);
+        } catch (IllegalArgumentException e) {
+            return INVALID_IDENTIFIER_VALUE;
+        }
+    }
+
+    /**
      * Returns a channel name that can be displayed to the user.
      *
-     * It's implemented only for radio technologies where the channel is meant
-     * to be presented to the user.
+     * <p>It's implemented only for radio technologies where the channel is meant
+     * to be presented to the user, such as FM/AM and HD radio.
+     *
+     * <p>For HD radio, the display name is prefix with "-HD[NUMBER]" where the number is the
+     * sub channel.
      *
      * @param sel the program selector
-     * @return Channel name or null, if radio technology doesn't present channel names to the user.
+     * @return Channel name or {@code null}, if radio technology doesn't present channel names to
+     * the user.
      */
     public static @Nullable String getDisplayName(@NonNull ProgramSelector sel,
             @NameFlag int flags) {
         boolean noProgramTypeFallback = (flags & NAME_NO_PROGRAM_TYPE_FALLBACK) != 0;
 
         if (isAmFmProgram(sel)) {
-            if (!hasId(sel, ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY)) {
+            long freq;
+            String hdSuffix = "";
+            if (sel.getPrimaryId().getType()
+                    == ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT) {
+                IdentifierExt.HdPrimary hdIdExt = IdentifierExt.asHdPrimary(sel.getPrimaryId());
+                freq = hdIdExt.getFrequency();
+                hdSuffix = "-HD" + (hdIdExt.getSubchannel() + 1);
+            } else if (hasId(sel, ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY)) {
+                freq = sel.getFirstId(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY);
+            } else {
                 if (noProgramTypeFallback) return null;
                 // if there is no frequency assigned, let's assume it's a malformed RDS selector
                 return "FM";
             }
-            long freq = sel.getFirstId(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY);
-            return formatAmFmFrequency(freq, flags);
+            return formatAmFmFrequency(freq, flags) + hdSuffix;
         }
 
         if ((flags & NAME_MODULATION_ONLY) != 0) return null;
@@ -285,7 +346,8 @@
         switch (sel.getPrimaryId().getType()) {
             case ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID:
                 return "SXM";
-            case ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC:
+            case ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT:
+            case ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT:
                 return "DAB";
             case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID:
                 return "DRMO";
@@ -442,10 +504,10 @@
      */
     public static class IdentifierExt {
         /**
-         * Decode {@link ProgramSelector#IDENTIFIER_TYPE_HD_STATION_ID_EXT} value.
+         * Decoder of {@link ProgramSelector#IDENTIFIER_TYPE_HD_STATION_ID_EXT} value.
          *
-         * @param id identifier to decode
-         * @return value decoder
+         * When pushed to the framework, it will be non-static class referring
+         * to the original value.
          */
         public static @Nullable HdPrimary asHdPrimary(@NonNull Identifier id) {
             if (id.getType() == ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT) {
@@ -482,5 +544,110 @@
                 return (int) ((mValue >>> (32 + 4)) & 0x3FFFF);
             }
         }
+
+        /**
+         * Decoder of {@link ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT} value.
+         *
+         * <p>When pushed to the framework, it will be non-static class referring
+         * to the original value.
+         * @param id Identifier to be decoded
+         * @return {@link DabPrimary} object if the identifier is DAB-type, {@code null} otherwise
+         */
+        public static @Nullable DabPrimary asDabPrimary(@NonNull Identifier id) {
+            if (id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) {
+                return new DabPrimary(id.getValue());
+            }
+            return null;
+        }
+
+        /**
+         * Decoder of {@link ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT} value.
+         *
+         * <p>When pushed to the framework, it will be non-static class referring
+         * to the original value.
+         */
+        public static class DabPrimary {
+            private final long mValue;
+
+            private DabPrimary(long value) {
+                mValue = value;
+            }
+
+            /**
+             * Get Service Identifier (SId).
+             * @return SId value
+             */
+            public int getSId() {
+                return (int) (mValue & 0xFFFFFFFF);
+            }
+
+            /**
+             * Get Extended Country Code (ECC)
+             * @return Extended Country Code
+             */
+            public int getEcc() {
+                return (int) ((mValue >>> 32) & 0xFF);
+            }
+
+            /**
+             * Get SCIdS (Service Component Identifier within the Service) value
+             * @return SCIdS value
+             */
+            public int getSCIdS() {
+                return (int) ((mValue >>> (32 + 8)) & 0xF);
+            }
+        }
+    }
+
+    public static class ProgramSelectorComparator implements Comparator<ProgramSelector> {
+        @Override
+        public int compare(ProgramSelector selector1, ProgramSelector selector2) {
+            int type1 = selector1.getPrimaryId().getType();
+            int type2 = selector2.getPrimaryId().getType();
+            int frequency1 = getFrequency(selector1);
+            int frequency2 = getFrequency(selector2);
+            if (isAmFmProgram(selector1) && isAmFmProgram(selector2)) {
+                if (frequency1 != frequency2) {
+                    return frequency1 > frequency2 ? 1 : -1;
+                }
+                int subchannel1 = selector1.getPrimaryId().getType()
+                        == ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT
+                        ? IdentifierExt.asHdPrimary(selector1.getPrimaryId()).getSubchannel() : 0;
+                int subchannel2 = selector2.getPrimaryId().getType()
+                        == ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT
+                        ? IdentifierExt.asHdPrimary(selector2.getPrimaryId()).getSubchannel() : 0;
+                if (subchannel1 != subchannel2) {
+                    return subchannel1 > subchannel2 ? 1 : -1;
+                }
+                return selector1.getPrimaryId().getType() - selector2.getPrimaryId().getType();
+            } else if (type1 == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT
+                    && type2 == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) {
+                if (frequency1 != frequency2) {
+                    return frequency1 > frequency2 ? 1 : -1;
+                }
+                IdentifierExt.DabPrimary dabPrimary1 = IdentifierExt.asDabPrimary(
+                        selector1.getPrimaryId());
+                IdentifierExt.DabPrimary dabPrimary2 = IdentifierExt.asDabPrimary(
+                        selector2.getPrimaryId());
+                int ecc1 = dabPrimary1.getEcc();
+                int ecc2 = dabPrimary2.getEcc();
+                if (ecc1 != ecc2) {
+                    return ecc1 > ecc2 ? 1 : -1;
+                }
+                int sId1 = dabPrimary1.getSId();
+                int sId2 = dabPrimary2.getSId();
+                if (sId1 != sId2) {
+                    return sId1 > sId2 ? 1 : -1;
+                }
+                int sCIds1 = dabPrimary1.getSCIdS();
+                int sCIds2 = dabPrimary2.getSCIdS();
+                if (sCIds1 != sCIds2) {
+                    return sCIds1 > sCIds2 ? 1 : -1;
+                }
+                return getDabEnsemble(selector1) > getDabEnsemble(selector2) ? 1 : -1;
+            }
+            return type1 > type2 || (type1 == type2 && selector1.getPrimaryId().getValue()
+                    > selector2.getPrimaryId().getValue()) ? 1 : -1;
+        }
     }
 }
diff --git a/car-qc-lib/Android.bp b/car-qc-lib/Android.bp
index f0ccd8b..1c2ac26 100644
--- a/car-qc-lib/Android.bp
+++ b/car-qc-lib/Android.bp
@@ -27,5 +27,6 @@
     static_libs: [
         "androidx.annotation_annotation",
         "car-ui-lib-no-overlayable",
+        "car-resource-common",
     ],
 }
diff --git a/car-qc-lib/res/color/qc_seekbar_thumb_selector.xml b/car-qc-lib/res/color/qc_seekbar_thumb_selector.xml
deleted file mode 100644
index bf94a61..0000000
--- a/car-qc-lib/res/color/qc_seekbar_thumb_selector.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?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
deleted file mode 100644
index e0bfc22..0000000
--- a/car-qc-lib/res/color/qc_switch_thumb_selector.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?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/values/styles.xml b/car-qc-lib/res/values/styles.xml
index 587b522..51a7d35 100644
--- a/car-qc-lib/res/values/styles.xml
+++ b/car-qc-lib/res/values/styles.xml
@@ -16,16 +16,15 @@
 
 <resources>
     <style name="TextAppearance.QC" parent="android:TextAppearance.DeviceDefault">
-        <item name="android:textColor">@color/car_ui_text_color_primary</item>
+        <item name="android:textColor">@color/car_on_surface</item>
     </style>
 
-    <style name="TextAppearance.QC.Title">
-        <item name="android:textSize">@dimen/car_ui_body1_size</item>
+    <style name="TextAppearance.QC.Title" parent="android:TextAppearance.DeviceDefault.Large">
+        <item name="android:textColor">@color/car_on_surface</item>
     </style>
 
-    <style name="TextAppearance.QC.Subtitle">
-        <item name="android:textColor">@color/car_ui_text_color_secondary</item>
-        <item name="android:textSize">@dimen/car_ui_body3_size</item>
+    <style name="TextAppearance.QC.Subtitle" parent="android:TextAppearance.DeviceDefault.Small">
+        <item name="android:textColor">@color/car_on_surface_variant</item>
     </style>
 
     <style name="Widget.QC" parent="android:Widget.DeviceDefault"/>
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 b7b9cd1..f4e92d1 100644
--- a/car-qc-lib/src/com/android/car/qc/QCActionItem.java
+++ b/car-qc-lib/src/com/android/car/qc/QCActionItem.java
@@ -31,18 +31,20 @@
 public class QCActionItem extends QCItem {
     private final boolean mIsChecked;
     private final boolean mIsAvailable;
+    private final boolean mIsClickable;
     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 String contentDescription, @Nullable PendingIntent action,
-            @Nullable PendingIntent disabledClickAction) {
+            boolean isAvailable, boolean isClickable, boolean isClickableWhileDisabled,
+            @Nullable Icon icon, @Nullable String contentDescription,
+            @Nullable PendingIntent action, @Nullable PendingIntent disabledClickAction) {
         super(type, isEnabled, isClickableWhileDisabled);
         mIsChecked = isChecked;
         mIsAvailable = isAvailable;
+        mIsClickable = isClickable;
         mIcon = icon;
         mContentDescription = contentDescription;
         mAction = action;
@@ -53,6 +55,7 @@
         super(in);
         mIsChecked = in.readBoolean();
         mIsAvailable = in.readBoolean();
+        mIsClickable = in.readBoolean();
         boolean hasIcon = in.readBoolean();
         if (hasIcon) {
             mIcon = Icon.CREATOR.createFromParcel(in);
@@ -76,6 +79,7 @@
         super.writeToParcel(dest, flags);
         dest.writeBoolean(mIsChecked);
         dest.writeBoolean(mIsAvailable);
+        dest.writeBoolean(mIsClickable);
         boolean includeIcon = getType().equals(QC_TYPE_ACTION_TOGGLE) && mIcon != null;
         dest.writeBoolean(includeIcon);
         if (includeIcon) {
@@ -116,6 +120,10 @@
         return mIsAvailable;
     }
 
+    public boolean isClickable() {
+        return mIsClickable;
+    }
+
     @Nullable
     public Icon getIcon() {
         return mIcon;
@@ -146,6 +154,7 @@
         private boolean mIsChecked;
         private boolean mIsEnabled = true;
         private boolean mIsAvailable = true;
+        private boolean mIsClickable = true;
         private boolean mIsClickableWhileDisabled = false;
         private Icon mIcon;
         private PendingIntent mAction;
@@ -184,6 +193,15 @@
         }
 
         /**
+         * Sets whether the action is clickable. This differs from available in that the style will
+         * remain as if it's enabled/available but click actions will not be processed.
+         */
+        public Builder setClickable(boolean clickable) {
+            mIsClickable = clickable;
+            return this;
+        }
+
+        /**
          * Sets whether or not an action item should be clickable while disabled.
          */
         public Builder setClickableWhileDisabled(boolean clickable) {
@@ -236,7 +254,7 @@
          * Builds the final {@link QCActionItem}.
          */
         public QCActionItem build() {
-            return new QCActionItem(mType, mIsChecked, mIsEnabled, mIsAvailable,
+            return new QCActionItem(mType, mIsChecked, mIsEnabled, mIsAvailable, mIsClickable,
                     mIsClickableWhileDisabled, mIcon, mContentDescription, mAction,
                     mDisabledClickAction);
         }
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 490615c..2045fd2 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
@@ -308,11 +308,10 @@
         CarUiUtils.makeAllViewsEnabled(switchView, action.isEnabled());
 
         boolean shouldEnableView =
-                (action.isEnabled() || action.isClickableWhileDisabled()) && action.isAvailable();
+                (action.isEnabled() || action.isClickableWhileDisabled()) && action.isAvailable()
+                && action.isClickable();
         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) -> {
@@ -341,7 +340,8 @@
         }
         DrawableStateToggleButton toggleButton = tmpToggleButton; // must be effectively final
         boolean shouldEnableView =
-                (action.isEnabled() || action.isClickableWhileDisabled()) && action.isAvailable();
+                (action.isEnabled() || action.isClickableWhileDisabled()) && action.isAvailable()
+                && action.isClickable();
         toggleButton.setText(null);
         toggleButton.setTextOn(null);
         toggleButton.setTextOff(null);
@@ -416,8 +416,6 @@
         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/tools/rro/resource_utils.py b/tools/rro/resource_utils.py
index fe4c52c..003c69e 100644
--- a/tools/rro/resource_utils.py
+++ b/tools/rro/resource_utils.py
@@ -15,10 +15,12 @@
 
 import os
 import re
+import sys
 try:
     import lxml.etree as etree
 except ImportError:
-    print("Please install 'lxml' python package and retry.")
+    print("Please install 'lxml' python package and retry. \n"
+        + "E.g., you can use 'sudo apt-get install python3-lxml'.")
     sys.exit(1)
 
 class ResourceLocation: