summaryrefslogtreecommitdiff
path: root/car-broadcastradio-support
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-05-10 15:46:41 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-05-10 15:46:41 +0000
commitee02976069cccb91375d06a2bcfeca3fccfc08bc (patch)
treebe9269d71941eec7871f22db4c1263682b782298 /car-broadcastradio-support
parent9e49fbb3afde2aafb36e70df56d0ba98a8c86030 (diff)
parentbcb1dc185374136d2b72d98a89c828ae063919b3 (diff)
downloadsystemlibs-ee02976069cccb91375d06a2bcfeca3fccfc08bc.tar.gz
Snap for 11819167 from bcb1dc185374136d2b72d98a89c828ae063919b3 to busytown-mac-infra-release
Change-Id: Ia5d306fe4ad636c4b7b665caf3d23487a4de84cb
Diffstat (limited to 'car-broadcastradio-support')
-rw-r--r--car-broadcastradio-support/Android.bp1
-rw-r--r--car-broadcastradio-support/OWNERS1
-rw-r--r--car-broadcastradio-support/src/com/android/car/broadcastradio/support/platform/ProgramInfoExt.java125
-rw-r--r--car-broadcastradio-support/src/com/android/car/broadcastradio/support/platform/ProgramSelectorExt.java187
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;
+ }
}
}