diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-05-10 15:46:41 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-05-10 15:46:41 +0000 |
commit | ee02976069cccb91375d06a2bcfeca3fccfc08bc (patch) | |
tree | be9269d71941eec7871f22db4c1263682b782298 /car-broadcastradio-support | |
parent | 9e49fbb3afde2aafb36e70df56d0ba98a8c86030 (diff) | |
parent | bcb1dc185374136d2b72d98a89c828ae063919b3 (diff) | |
download | systemlibs-ee02976069cccb91375d06a2bcfeca3fccfc08bc.tar.gz |
Snap for 11819167 from bcb1dc185374136d2b72d98a89c828ae063919b3 to busytown-mac-infra-release
Change-Id: Ia5d306fe4ad636c4b7b665caf3d23487a4de84cb
Diffstat (limited to 'car-broadcastradio-support')
4 files changed, 291 insertions, 23 deletions
diff --git a/car-broadcastradio-support/Android.bp b/car-broadcastradio-support/Android.bp index d0e24e1..007a65f 100644 --- a/car-broadcastradio-support/Android.bp +++ b/car-broadcastradio-support/Android.bp @@ -15,6 +15,7 @@ // package { + default_team: "trendy_team_aaos_framework", default_applicable_licenses: ["Android-Apache-2.0"], } diff --git a/car-broadcastradio-support/OWNERS b/car-broadcastradio-support/OWNERS index 7d3fbc0..95b6918 100644 --- a/car-broadcastradio-support/OWNERS +++ b/car-broadcastradio-support/OWNERS @@ -3,4 +3,3 @@ 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..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 @@ -21,6 +21,7 @@ import android.annotation.NonNull; 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,8 @@ import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Comparator; +import java.util.Objects; /** * Proposed extensions to android.hardware.radio.RadioManager.ProgramInfo. @@ -71,13 +74,23 @@ public class ProgramInfoExt { /** * 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 +117,17 @@ public class ProgramInfoExt { } /** + * 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 +142,75 @@ public class ProgramInfoExt { } /** - * Converts {@ProgramInfo} to {@MediaMetadata}. + * Converts {@link ProgramInfo} to {@link MediaMetadata} for displaying. + * + * <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 {@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; + 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); + 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}. * - * 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 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 * @return {@link MediaMetadata} object */ @@ -158,16 +245,30 @@ public class ProgramInfoExt { } 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; + } + 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; + } } } |