/* * Copyright (C) 2015 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. */ package android.hardware.radio; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Set; /** * Contains meta data about a radio program such as station name, song title, artist etc... * @hide */ @SystemApi public final class RadioMetadata implements Parcelable { private static final String TAG = "BroadcastRadio.metadata"; /** * The RDS Program Information. */ public static final String METADATA_KEY_RDS_PI = "android.hardware.radio.metadata.RDS_PI"; /** * The RDS Program Service. */ public static final String METADATA_KEY_RDS_PS = "android.hardware.radio.metadata.RDS_PS"; /** * The RDS PTY. */ public static final String METADATA_KEY_RDS_PTY = "android.hardware.radio.metadata.RDS_PTY"; /** * The RBDS PTY. */ public static final String METADATA_KEY_RBDS_PTY = "android.hardware.radio.metadata.RBDS_PTY"; /** * The RBDS Radio Text. */ public static final String METADATA_KEY_RDS_RT = "android.hardware.radio.metadata.RDS_RT"; /** * The song title. */ public static final String METADATA_KEY_TITLE = "android.hardware.radio.metadata.TITLE"; /** * The artist name. */ public static final String METADATA_KEY_ARTIST = "android.hardware.radio.metadata.ARTIST"; /** * The album name. */ public static final String METADATA_KEY_ALBUM = "android.hardware.radio.metadata.ALBUM"; /** * The music genre. */ public static final String METADATA_KEY_GENRE = "android.hardware.radio.metadata.GENRE"; /** * The radio station icon {@link Bitmap}. */ public static final String METADATA_KEY_ICON = "android.hardware.radio.metadata.ICON"; /** * The artwork for the song/album {@link Bitmap}. */ public static final String METADATA_KEY_ART = "android.hardware.radio.metadata.ART"; /** * The clock. */ public static final String METADATA_KEY_CLOCK = "android.hardware.radio.metadata.CLOCK"; /** * Technology-independent program name (station name). */ public static final String METADATA_KEY_PROGRAM_NAME = "android.hardware.radio.metadata.PROGRAM_NAME"; /** * DAB ensemble name. */ public static final String METADATA_KEY_DAB_ENSEMBLE_NAME = "android.hardware.radio.metadata.DAB_ENSEMBLE_NAME"; /** * DAB ensemble name - short version (up to 8 characters). */ public static final String METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT = "android.hardware.radio.metadata.DAB_ENSEMBLE_NAME_SHORT"; /** * DAB service name. */ public static final String METADATA_KEY_DAB_SERVICE_NAME = "android.hardware.radio.metadata.DAB_SERVICE_NAME"; /** * DAB service name - short version (up to 8 characters). */ public static final String METADATA_KEY_DAB_SERVICE_NAME_SHORT = "android.hardware.radio.metadata.DAB_SERVICE_NAME_SHORT"; /** * DAB component name. */ public static final String METADATA_KEY_DAB_COMPONENT_NAME = "android.hardware.radio.metadata.DAB_COMPONENT_NAME"; /** * DAB component name. */ public static final String METADATA_KEY_DAB_COMPONENT_NAME_SHORT = "android.hardware.radio.metadata.DAB_COMPONENT_NAME_SHORT"; /** * Short context description of comment * *

Comment could relate to the current audio program content, or it might * be unrelated information that the station chooses to send. It is composed * of short content description and actual text (see NRSC-G200-A and id3v2.3.0 * for more info). */ @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) public static final String METADATA_KEY_COMMENT_SHORT_DESCRIPTION = "android.hardware.radio.metadata.COMMENT_SHORT_DESCRIPTION"; /** * Actual text of comment * * @see #METADATA_KEY_COMMENT_SHORT_DESCRIPTION */ @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) public static final String METADATA_KEY_COMMENT_ACTUAL_TEXT = "android.hardware.radio.metadata.COMMENT_ACTUAL_TEXT"; /** * Commercial * *

Commercial is application specific and generally used to facilitate the * sale of products and services (see NRSC-G200-A and id3v2.3.0 for more info). */ @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) public static final String METADATA_KEY_COMMERCIAL = "android.hardware.radio.metadata.COMMERCIAL"; /** * Array of Unique File Identifiers * *

Unique File Identifier (UFID) can be used to transmit an alphanumeric * identifier of the current content, or of an advertised product or * service (see NRSC-G200-A and id3v2.3.0 for more info). */ @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) public static final String METADATA_KEY_UFIDS = "android.hardware.radio.metadata.UFIDS"; /** * HD short station name or HD universal short station name * *

It can be up to 12 characters (see SY_IDD_1020s for more info). */ @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) public static final String METADATA_KEY_HD_STATION_NAME_SHORT = "android.hardware.radio.metadata.HD_STATION_NAME_SHORT"; /** * HD long station name, HD station slogan or HD station message * *

(see SY_IDD_1020s for more info) */ @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) public static final String METADATA_KEY_HD_STATION_NAME_LONG = "android.hardware.radio.metadata.HD_STATION_NAME_LONG"; /** * Bit mask of all HD Radio subchannels available * *

Bit {@link ProgramSelector#SUB_CHANNEL_HD_1} from LSB represents the * availability of HD-1 subchannel (main program service, MPS). Bits * {@link ProgramSelector#SUB_CHANNEL_HD_2} to {@link ProgramSelector#SUB_CHANNEL_HD_8} * from LSB represent HD-2 to HD-8 subchannel (supplemental program services, SPS) * respectively. */ @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) public static final String METADATA_KEY_HD_SUBCHANNELS_AVAILABLE = "android.hardware.radio.metadata.HD_SUBCHANNELS_AVAILABLE"; private static final int METADATA_TYPE_INVALID = -1; private static final int METADATA_TYPE_INT = 0; private static final int METADATA_TYPE_TEXT = 1; private static final int METADATA_TYPE_BITMAP = 2; private static final int METADATA_TYPE_CLOCK = 3; private static final int METADATA_TYPE_TEXT_ARRAY = 4; private static final ArrayMap METADATA_KEYS_TYPE; static { METADATA_KEYS_TYPE = new ArrayMap(); METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PI, METADATA_TYPE_INT); METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PS, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PTY, METADATA_TYPE_INT); METADATA_KEYS_TYPE.put(METADATA_KEY_RBDS_PTY, METADATA_TYPE_INT); METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_RT, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_ICON, METADATA_TYPE_BITMAP); METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP); METADATA_KEYS_TYPE.put(METADATA_KEY_CLOCK, METADATA_TYPE_CLOCK); METADATA_KEYS_TYPE.put(METADATA_KEY_PROGRAM_NAME, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_ENSEMBLE_NAME, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_SERVICE_NAME, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_SERVICE_NAME_SHORT, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_COMPONENT_NAME, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_COMPONENT_NAME_SHORT, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_COMMENT_SHORT_DESCRIPTION, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_COMMENT_ACTUAL_TEXT, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_COMMERCIAL, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_UFIDS, METADATA_TYPE_TEXT_ARRAY); METADATA_KEYS_TYPE.put(METADATA_KEY_HD_STATION_NAME_SHORT, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_HD_STATION_NAME_LONG, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_HD_SUBCHANNELS_AVAILABLE, METADATA_TYPE_INT); } // keep in sync with: system/media/radio/include/system/radio_metadata.h private static final int NATIVE_KEY_INVALID = -1; private static final int NATIVE_KEY_RDS_PI = 0; private static final int NATIVE_KEY_RDS_PS = 1; private static final int NATIVE_KEY_RDS_PTY = 2; private static final int NATIVE_KEY_RBDS_PTY = 3; private static final int NATIVE_KEY_RDS_RT = 4; private static final int NATIVE_KEY_TITLE = 5; private static final int NATIVE_KEY_ARTIST = 6; private static final int NATIVE_KEY_ALBUM = 7; private static final int NATIVE_KEY_GENRE = 8; private static final int NATIVE_KEY_ICON = 9; private static final int NATIVE_KEY_ART = 10; private static final int NATIVE_KEY_CLOCK = 11; private static final SparseArray NATIVE_KEY_MAPPING; static { NATIVE_KEY_MAPPING = new SparseArray(); NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PI, METADATA_KEY_RDS_PI); NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PS, METADATA_KEY_RDS_PS); NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PTY, METADATA_KEY_RDS_PTY); NATIVE_KEY_MAPPING.put(NATIVE_KEY_RBDS_PTY, METADATA_KEY_RBDS_PTY); NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_RT, METADATA_KEY_RDS_RT); NATIVE_KEY_MAPPING.put(NATIVE_KEY_TITLE, METADATA_KEY_TITLE); NATIVE_KEY_MAPPING.put(NATIVE_KEY_ARTIST, METADATA_KEY_ARTIST); NATIVE_KEY_MAPPING.put(NATIVE_KEY_ALBUM, METADATA_KEY_ALBUM); NATIVE_KEY_MAPPING.put(NATIVE_KEY_GENRE, METADATA_KEY_GENRE); NATIVE_KEY_MAPPING.put(NATIVE_KEY_ICON, METADATA_KEY_ICON); NATIVE_KEY_MAPPING.put(NATIVE_KEY_ART, METADATA_KEY_ART); NATIVE_KEY_MAPPING.put(NATIVE_KEY_CLOCK, METADATA_KEY_CLOCK); } /** * Provides a Clock that can be used to describe time as provided by the Radio. * *

The clock time is defined by the seconds since epoch at the UTC + 0 timezone * and timezone offset from UTC + 0 represented in number of minutes. * * @hide */ @SystemApi public static final class Clock implements Parcelable { private final long mUtcEpochSeconds; private final int mTimezoneOffsetMinutes; public int describeContents() { return 0; } public void writeToParcel(Parcel out, int flags) { out.writeLong(mUtcEpochSeconds); out.writeInt(mTimezoneOffsetMinutes); } public static final @android.annotation.NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { public Clock createFromParcel(Parcel in) { return new Clock(in); } public Clock[] newArray(int size) { return new Clock[size]; } }; public Clock(long utcEpochSeconds, int timezoneOffsetMinutes) { mUtcEpochSeconds = utcEpochSeconds; mTimezoneOffsetMinutes = timezoneOffsetMinutes; } private Clock(Parcel in) { mUtcEpochSeconds = in.readLong(); mTimezoneOffsetMinutes = in.readInt(); } public long getUtcEpochSeconds() { return mUtcEpochSeconds; } public int getTimezoneOffsetMinutes() { return mTimezoneOffsetMinutes; } } private final Bundle mBundle; // Lazily computed hash code based upon the contents of mBundle. private Integer mHashCode; @Override public int hashCode() { if (mHashCode == null) { List keys = new ArrayList(mBundle.keySet()); keys.sort(null); Object[] objs = new Object[2 * keys.size()]; for (int i = 0; i < keys.size(); i++) { objs[2 * i] = keys.get(i); objs[2 * i + 1] = mBundle.get(keys.get(i)); } mHashCode = Arrays.hashCode(objs); } return mHashCode; } @Override public boolean equals(@Nullable Object obj) { if (this == obj) return true; if (!(obj instanceof RadioMetadata)) return false; Bundle otherBundle = ((RadioMetadata) obj).mBundle; if (!mBundle.keySet().equals(otherBundle.keySet())) { return false; } for (String key : mBundle.keySet()) { if (Flags.hdRadioImproved() && Objects.equals(METADATA_KEYS_TYPE.get(key), METADATA_TYPE_TEXT_ARRAY)) { if (!Arrays.equals(mBundle.getStringArray(key), otherBundle.getStringArray(key))) { return false; } } else if (!Objects.equals(mBundle.get(key), otherBundle.get(key))) { return false; } } return true; } RadioMetadata() { mBundle = new Bundle(); } private RadioMetadata(Bundle bundle) { mBundle = new Bundle(bundle); } private RadioMetadata(Parcel in) { mBundle = in.readBundle(); } @NonNull @Override public String toString() { StringBuilder sb = new StringBuilder("RadioMetadata["); final String removePrefix = "android.hardware.radio.metadata"; boolean first = true; for (String key : mBundle.keySet()) { if (first) first = false; else sb.append(", "); String keyDisp = key; if (key.startsWith(removePrefix)) keyDisp = key.substring(removePrefix.length()); sb.append(keyDisp); sb.append('='); if (Flags.hdRadioImproved() && Objects.equals(METADATA_KEYS_TYPE.get(key), METADATA_TYPE_TEXT_ARRAY)) { String[] stringArrayValue = mBundle.getStringArray(key); sb.append('['); for (int i = 0; i < stringArrayValue.length; i++) { if (i != 0) { sb.append(','); } sb.append(stringArrayValue[i]); } sb.append(']'); } else { sb.append(mBundle.get(key)); } } sb.append("]"); return sb.toString(); } /** * Returns {@code true} if the given key is contained in the meta data * * @param key a String key * @return {@code true} if the key exists in this meta data, {@code false} otherwise */ public boolean containsKey(String key) { return mBundle.containsKey(key); } /** * Returns the text value associated with the given key as a String, or null * if the key is not found in the meta data. * * @param key The key the value is stored under * @return a String value, or null */ public String getString(String key) { return mBundle.getString(key); } private static void putInt(Bundle bundle, String key, int value) { int type = METADATA_KEYS_TYPE.getOrDefault(key, METADATA_TYPE_INVALID); if (type != METADATA_TYPE_INT && type != METADATA_TYPE_BITMAP) { throw new IllegalArgumentException("The " + key + " key cannot be used to put an int"); } bundle.putInt(key, value); } /** * Returns the value associated with the given key, * or 0 if the key is not found in the meta data. * * @param key The key the value is stored under * @return an int value */ public int getInt(String key) { return mBundle.getInt(key, 0); } /** * Returns a {@link Bitmap} for the given key or null if the key is not found in the meta data. * * @param key The key the value is stored under * @return a {@link Bitmap} or null * @deprecated Use getBitmapId(String) instead */ @Deprecated public Bitmap getBitmap(String key) { Bitmap bmp = null; try { bmp = mBundle.getParcelable(key, android.graphics.Bitmap.class); } catch (Exception e) { // ignore, value was not a bitmap Log.w(TAG, "Failed to retrieve a key as Bitmap.", e); } return bmp; } /** * Retrieves an identifier for a bitmap. * *

The format of an identifier is opaque to the application, * with a special case of value 0 being invalid. * An identifier for a given image-tuner pair is unique, so an application * may cache images and determine if there is a necessity to fetch them * again - if identifier changes, it means the image has changed. * *

Only bitmap keys may be used with this method: *

* * @param key The key the value is stored under. * @return a bitmap identifier or 0 if it's missing. * @throws NullPointerException if metadata key is {@code null} * @throws IllegalArgumentException if the metadata with the key is not found in * metadata or the key is not of bitmap-key type */ @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) public int getBitmapId(@NonNull String key) { Objects.requireNonNull(key, "Metadata key can not be null"); if (!METADATA_KEY_ICON.equals(key) && !METADATA_KEY_ART.equals(key)) { throw new IllegalArgumentException("Failed to retrieve key " + key + " as bitmap key"); } return getInt(key); } public Clock getClock(String key) { Clock clock = null; try { clock = mBundle.getParcelable(key, android.hardware.radio.RadioMetadata.Clock.class); } catch (Exception e) { // ignore, value was not a clock. Log.w(TAG, "Failed to retrieve a key as Clock.", e); } return clock; } /** * Gets the string array value associated with the given key as a string * array. * *

Only string array keys may be used with this method: *

* * @param key The key the value is stored under * @return String array of the given string-array-type key * @throws NullPointerException if metadata key is {@code null} * @throws IllegalArgumentException if the metadata with the key is not found in * metadata or the key is not of string-array type */ @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) @NonNull public String[] getStringArray(@NonNull String key) { Objects.requireNonNull(key, "Metadata key can not be null"); if (!Objects.equals(METADATA_KEYS_TYPE.get(key), METADATA_TYPE_TEXT_ARRAY)) { throw new IllegalArgumentException("Failed to retrieve key " + key + " as string array"); } String[] stringArrayValue = mBundle.getStringArray(key); if (stringArrayValue == null) { throw new IllegalArgumentException("Key " + key + " is not found in metadata"); } return stringArrayValue; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeBundle(mBundle); } /** * Returns the number of fields in this meta data. * * @return the number of fields in the meta data. */ public int size() { return mBundle.size(); } /** * Returns a Set containing the Strings used as keys in this meta data. * * @return a Set of String keys */ public Set keySet() { return mBundle.keySet(); } /** * Helper for getting the String key used by {@link RadioMetadata} from the * corrsponding native integer key. * * @param nativeKey The key used by the editor * @return the key used by this class or null if no mapping exists * @hide */ public static String getKeyFromNativeKey(int nativeKey) { return NATIVE_KEY_MAPPING.get(nativeKey, null); } public static final @android.annotation.NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public RadioMetadata createFromParcel(Parcel in) { return new RadioMetadata(in); } @Override public RadioMetadata[] newArray(int size) { return new RadioMetadata[size]; } }; /** * Use to build RadioMetadata objects. */ public static final class Builder { private final Bundle mBundle; /** * Create an empty Builder. Any field that should be included in the * {@link RadioMetadata} must be added. */ public Builder() { mBundle = new Bundle(); } /** * Create a Builder using a {@link RadioMetadata} instance to set the * initial values. All fields in the source meta data will be included in * the new meta data. Fields can be overwritten by adding the same key. * * @param source */ public Builder(RadioMetadata source) { mBundle = new Bundle(source.mBundle); } /** * Create a Builder using a {@link RadioMetadata} instance to set * initial values, but replace bitmaps with a scaled down copy if they * are larger than maxBitmapSize. * * @param source The original meta data to copy. * @param maxBitmapSize The maximum height/width for bitmaps contained * in the meta data. * @hide */ public Builder(RadioMetadata source, int maxBitmapSize) { this(source); for (String key : mBundle.keySet()) { Object value = mBundle.get(key); if (value != null && value instanceof Bitmap) { Bitmap bmp = (Bitmap) value; if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) { putBitmap(key, scaleBitmap(bmp, maxBitmapSize)); } } } } /** * Put a String value into the meta data. Custom keys may be used, but if * the METADATA_KEYs defined in this class are used they may only be one * of the following: *
    *
  • {@link #METADATA_KEY_RDS_PS}
  • *
  • {@link #METADATA_KEY_RDS_RT}
  • *
  • {@link #METADATA_KEY_TITLE}
  • *
  • {@link #METADATA_KEY_ARTIST}
  • *
  • {@link #METADATA_KEY_ALBUM}
  • *
  • {@link #METADATA_KEY_GENRE}
  • *
  • {@link #METADATA_KEY_COMMENT_SHORT_DESCRIPTION}
  • *
  • {@link #METADATA_KEY_COMMENT_ACTUAL_TEXT}
  • *
  • {@link #METADATA_KEY_COMMERCIAL}
  • *
  • {@link #METADATA_KEY_HD_STATION_NAME_SHORT}
  • *
  • {@link #METADATA_KEY_HD_STATION_NAME_LONG}
  • *
* * @param key The key for referencing this value * @param value The String value to store * @return the same Builder instance */ public Builder putString(String key, String value) { if (!METADATA_KEYS_TYPE.containsKey(key) || METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) { throw new IllegalArgumentException("The " + key + " key cannot be used to put a String"); } mBundle.putString(key, value); return this; } /** * Put an int value into the meta data. Custom keys may be used, but if * the METADATA_KEYs defined in this class are used they may only be one * of the following: *
    *
  • {@link #METADATA_KEY_RDS_PI}
  • *
  • {@link #METADATA_KEY_RDS_PTY}
  • *
  • {@link #METADATA_KEY_RBDS_PTY}
  • *
  • {@link #METADATA_KEY_HD_SUBCHANNELS_AVAILABLE}
  • *
* or any bitmap represented by its identifier. * * @param key The key for referencing this value * @param value The int value to store * @return the same Builder instance */ public Builder putInt(String key, int value) { RadioMetadata.putInt(mBundle, key, value); return this; } /** * Put a {@link Bitmap} into the meta data. Custom keys may be used, but * if the METADATA_KEYs defined in this class are used they may only be * one of the following: *
    *
  • {@link #METADATA_KEY_ICON}
  • *
  • {@link #METADATA_KEY_ART}
  • *
*

* * @param key The key for referencing this value * @param value The Bitmap to store * @return the same Builder instance */ public Builder putBitmap(String key, Bitmap value) { if (!METADATA_KEYS_TYPE.containsKey(key) || METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) { throw new IllegalArgumentException("The " + key + " key cannot be used to put a Bitmap"); } mBundle.putParcelable(key, value); return this; } /** * Put a {@link RadioMetadata.Clock} into the meta data. Custom keys may be used, but if the * METADATA_KEYs defined in this class are used they may only be one of the following: *

    *
  • {@link #METADATA_KEY_CLOCK}
  • *
* * @param utcSecondsSinceEpoch Number of seconds since epoch for UTC + 0 timezone. * @param timezoneOffsetMinutes Offset of timezone from UTC + 0 in minutes. * @return the same Builder instance. */ public Builder putClock(String key, long utcSecondsSinceEpoch, int timezoneOffsetMinutes) { if (!METADATA_KEYS_TYPE.containsKey(key) || METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_CLOCK) { throw new IllegalArgumentException("The " + key + " key cannot be used to put a RadioMetadata.Clock."); } mBundle.putParcelable(key, new Clock(utcSecondsSinceEpoch, timezoneOffsetMinutes)); return this; } /** * Put a String array into the meta data. Custom keys may be used, but if * the METADATA_KEYs defined in this class are used they may only be one * of the following: *
    *
  • {@link #METADATA_KEY_UFIDS}
  • *
* * @param key The key for referencing this value * @param value The String value to store * @return the same Builder instance * @throws NullPointerException if key or value is null * @throws IllegalArgumentException if the key is not string-array-type key */ @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) @NonNull public Builder putStringArray(@NonNull String key, @NonNull String[] value) { Objects.requireNonNull(key, "Key can not be null"); Objects.requireNonNull(value, "Value can not be null"); if (!METADATA_KEYS_TYPE.containsKey(key) || !Objects.equals(METADATA_KEYS_TYPE.get(key), METADATA_TYPE_TEXT_ARRAY)) { throw new IllegalArgumentException("The " + key + " key cannot be used to put a RadioMetadata String Array."); } mBundle.putStringArray(key, value); return this; } /** * Creates a {@link RadioMetadata} instance with the specified fields. * * @return a new {@link RadioMetadata} object */ public RadioMetadata build() { return new RadioMetadata(mBundle); } private Bitmap scaleBitmap(Bitmap bmp, int maxSize) { float maxSizeF = maxSize; float widthScale = maxSizeF / bmp.getWidth(); float heightScale = maxSizeF / bmp.getHeight(); float scale = Math.min(widthScale, heightScale); int height = (int) (bmp.getHeight() * scale); int width = (int) (bmp.getWidth() * scale); return Bitmap.createScaledBitmap(bmp, width, height, true); } } int putIntFromNative(int nativeKey, int value) { String key = getKeyFromNativeKey(nativeKey); try { putInt(mBundle, key, value); // Invalidate mHashCode to force it to be recomputed. mHashCode = null; return 0; } catch (IllegalArgumentException ex) { return -1; } } int putStringFromNative(int nativeKey, String value) { String key = getKeyFromNativeKey(nativeKey); if (!METADATA_KEYS_TYPE.containsKey(key) || METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) { return -1; } mBundle.putString(key, value); // Invalidate mHashCode to force it to be recomputed. mHashCode = null; return 0; } int putBitmapFromNative(int nativeKey, byte[] value) { String key = getKeyFromNativeKey(nativeKey); if (!METADATA_KEYS_TYPE.containsKey(key) || METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) { return -1; } Bitmap bmp = null; try { bmp = BitmapFactory.decodeByteArray(value, 0, value.length); if (bmp != null) { mBundle.putParcelable(key, bmp); // Invalidate mHashCode to force it to be recomputed. mHashCode = null; return 0; } } catch (Exception e) { } return -1; } int putClockFromNative(int nativeKey, long utcEpochSeconds, int timezoneOffsetInMinutes) { String key = getKeyFromNativeKey(nativeKey); if (!METADATA_KEYS_TYPE.containsKey(key) || METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_CLOCK) { return -1; } mBundle.putParcelable(key, new RadioMetadata.Clock( utcEpochSeconds, timezoneOffsetInMinutes)); // Invalidate mHashCode to force it to be recomputed. mHashCode = null; return 0; } }