diff options
author | Xin Li <delphij@google.com> | 2024-01-17 22:14:18 -0800 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2024-01-17 22:14:18 -0800 |
commit | 9b0e4dd209cba5e01706b0b80f135e84e777f66f (patch) | |
tree | 1f62dfb2dec5ac3a1322453dedfa107bac2f844b | |
parent | 34e33e461d20302c3672472e6f64d04d95173cf7 (diff) | |
parent | 6e3901f05074dda88fd426c49511a1ea0901f440 (diff) | |
download | systemlibs-9b0e4dd209cba5e01706b0b80f135e84e777f66f.tar.gz |
Merge Android 24Q1 Release (ab/11220357)
Bug: 319669529
Merged-In: I796c209ea3e4085e60d2ca2ba104b9cf88811894
Change-Id: Ia56aae54675e45ffe56c5506bcc73fff5d04cd31
9 files changed, 229 insertions, 73 deletions
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 android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Comparator; import java.util.Objects; /** @@ -171,8 +172,14 @@ public class ProgramInfoExt { 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 @@ public class ProgramInfoExt { } 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.Retention; 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 @@ public class ProgramSelectorExt { @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 @@ public class ProgramSelectorExt { } /** + * 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 @@ public class ProgramSelectorExt { 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 class ProgramSelectorExt { */ 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 @@ public class ProgramSelectorExt { 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 @@ android_library { 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 @@ import androidx.annotation.StringRes; 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 @@ public class QCActionItem extends QCItem { 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 @@ public class QCActionItem extends QCItem { 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 @@ public class QCActionItem extends QCItem { return mIsAvailable; } + public boolean isClickable() { + return mIsClickable; + } + @Nullable public Icon getIcon() { return mIcon; @@ -146,6 +154,7 @@ public class QCActionItem extends QCItem { 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 @@ public class QCActionItem extends QCItem { } /** + * 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 @@ public class QCActionItem extends QCItem { * 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 @@ public class QCRowView extends FrameLayout { 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 @@ public class QCRowView extends FrameLayout { } 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 @@ public class QCRowView extends FrameLayout { 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: |