Home | History | Annotate | Download | only in radio
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package android.hardware.radio;
     17 
     18 import android.annotation.NonNull;
     19 import android.annotation.SystemApi;
     20 import android.graphics.Bitmap;
     21 import android.graphics.BitmapFactory;
     22 import android.os.Bundle;
     23 import android.os.Parcel;
     24 import android.os.Parcelable;
     25 import android.util.ArrayMap;
     26 import android.util.Log;
     27 import android.util.SparseArray;
     28 
     29 import java.util.Set;
     30 
     31 /**
     32  * Contains meta data about a radio program such as station name, song title, artist etc...
     33  * @hide
     34  */
     35 @SystemApi
     36 public final class RadioMetadata implements Parcelable {
     37     private static final String TAG = "BroadcastRadio.metadata";
     38 
     39     /**
     40      * The RDS Program Information.
     41      */
     42     public static final String METADATA_KEY_RDS_PI = "android.hardware.radio.metadata.RDS_PI";
     43 
     44     /**
     45      * The RDS Program Service.
     46      */
     47     public static final String METADATA_KEY_RDS_PS = "android.hardware.radio.metadata.RDS_PS";
     48 
     49     /**
     50      * The RDS PTY.
     51      */
     52     public static final String METADATA_KEY_RDS_PTY = "android.hardware.radio.metadata.RDS_PTY";
     53 
     54     /**
     55      * The RBDS PTY.
     56      */
     57     public static final String METADATA_KEY_RBDS_PTY = "android.hardware.radio.metadata.RBDS_PTY";
     58 
     59     /**
     60      * The RBDS Radio Text.
     61      */
     62     public static final String METADATA_KEY_RDS_RT = "android.hardware.radio.metadata.RDS_RT";
     63 
     64     /**
     65      * The song title.
     66      */
     67     public static final String METADATA_KEY_TITLE = "android.hardware.radio.metadata.TITLE";
     68 
     69     /**
     70      * The artist name.
     71      */
     72     public static final String METADATA_KEY_ARTIST = "android.hardware.radio.metadata.ARTIST";
     73 
     74     /**
     75      * The album name.
     76      */
     77     public static final String METADATA_KEY_ALBUM = "android.hardware.radio.metadata.ALBUM";
     78 
     79     /**
     80      * The music genre.
     81      */
     82     public static final String METADATA_KEY_GENRE = "android.hardware.radio.metadata.GENRE";
     83 
     84     /**
     85      * The radio station icon {@link Bitmap}.
     86      */
     87     public static final String METADATA_KEY_ICON = "android.hardware.radio.metadata.ICON";
     88 
     89     /**
     90      * The artwork for the song/album {@link Bitmap}.
     91      */
     92     public static final String METADATA_KEY_ART = "android.hardware.radio.metadata.ART";
     93 
     94     /**
     95      * The clock.
     96      */
     97     public static final String METADATA_KEY_CLOCK = "android.hardware.radio.metadata.CLOCK";
     98 
     99     /**
    100      * Technology-independent program name (station name).
    101      */
    102     public static final String METADATA_KEY_PROGRAM_NAME =
    103             "android.hardware.radio.metadata.PROGRAM_NAME";
    104 
    105     /**
    106      * DAB ensemble name.
    107      */
    108     public static final String METADATA_KEY_DAB_ENSEMBLE_NAME =
    109             "android.hardware.radio.metadata.DAB_ENSEMBLE_NAME";
    110 
    111     /**
    112      * DAB ensemble name - short version (up to 8 characters).
    113      */
    114     public static final String METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT =
    115             "android.hardware.radio.metadata.DAB_ENSEMBLE_NAME_SHORT";
    116 
    117     /**
    118      * DAB service name.
    119      */
    120     public static final String METADATA_KEY_DAB_SERVICE_NAME =
    121             "android.hardware.radio.metadata.DAB_SERVICE_NAME";
    122 
    123     /**
    124      * DAB service name - short version (up to 8 characters).
    125      */
    126     public static final String METADATA_KEY_DAB_SERVICE_NAME_SHORT =
    127             "android.hardware.radio.metadata.DAB_SERVICE_NAME_SHORT";
    128 
    129     /**
    130      * DAB component name.
    131      */
    132     public static final String METADATA_KEY_DAB_COMPONENT_NAME =
    133             "android.hardware.radio.metadata.DAB_COMPONENT_NAME";
    134 
    135     /**
    136      * DAB component name.
    137      */
    138     public static final String METADATA_KEY_DAB_COMPONENT_NAME_SHORT =
    139             "android.hardware.radio.metadata.DAB_COMPONENT_NAME_SHORT";
    140 
    141 
    142     private static final int METADATA_TYPE_INVALID = -1;
    143     private static final int METADATA_TYPE_INT = 0;
    144     private static final int METADATA_TYPE_TEXT = 1;
    145     private static final int METADATA_TYPE_BITMAP = 2;
    146     private static final int METADATA_TYPE_CLOCK = 3;
    147 
    148     private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
    149 
    150     static {
    151         METADATA_KEYS_TYPE = new ArrayMap<String, Integer>();
    152         METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PI, METADATA_TYPE_INT);
    153         METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PS, METADATA_TYPE_TEXT);
    154         METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PTY, METADATA_TYPE_INT);
    155         METADATA_KEYS_TYPE.put(METADATA_KEY_RBDS_PTY, METADATA_TYPE_INT);
    156         METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_RT, METADATA_TYPE_TEXT);
    157         METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
    158         METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
    159         METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
    160         METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
    161         METADATA_KEYS_TYPE.put(METADATA_KEY_ICON, METADATA_TYPE_BITMAP);
    162         METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
    163         METADATA_KEYS_TYPE.put(METADATA_KEY_CLOCK, METADATA_TYPE_CLOCK);
    164         METADATA_KEYS_TYPE.put(METADATA_KEY_PROGRAM_NAME, METADATA_TYPE_TEXT);
    165         METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_ENSEMBLE_NAME, METADATA_TYPE_TEXT);
    166         METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT, METADATA_TYPE_TEXT);
    167         METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_SERVICE_NAME, METADATA_TYPE_TEXT);
    168         METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_SERVICE_NAME_SHORT, METADATA_TYPE_TEXT);
    169         METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_COMPONENT_NAME, METADATA_TYPE_TEXT);
    170         METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_COMPONENT_NAME_SHORT, METADATA_TYPE_TEXT);
    171     }
    172 
    173     // keep in sync with: system/media/radio/include/system/radio_metadata.h
    174     private static final int NATIVE_KEY_INVALID     = -1;
    175     private static final int NATIVE_KEY_RDS_PI      = 0;
    176     private static final int NATIVE_KEY_RDS_PS      = 1;
    177     private static final int NATIVE_KEY_RDS_PTY     = 2;
    178     private static final int NATIVE_KEY_RBDS_PTY    = 3;
    179     private static final int NATIVE_KEY_RDS_RT      = 4;
    180     private static final int NATIVE_KEY_TITLE       = 5;
    181     private static final int NATIVE_KEY_ARTIST      = 6;
    182     private static final int NATIVE_KEY_ALBUM       = 7;
    183     private static final int NATIVE_KEY_GENRE       = 8;
    184     private static final int NATIVE_KEY_ICON        = 9;
    185     private static final int NATIVE_KEY_ART         = 10;
    186     private static final int NATIVE_KEY_CLOCK       = 11;
    187 
    188     private static final SparseArray<String> NATIVE_KEY_MAPPING;
    189 
    190     static {
    191         NATIVE_KEY_MAPPING = new SparseArray<String>();
    192         NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PI, METADATA_KEY_RDS_PI);
    193         NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PS, METADATA_KEY_RDS_PS);
    194         NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PTY, METADATA_KEY_RDS_PTY);
    195         NATIVE_KEY_MAPPING.put(NATIVE_KEY_RBDS_PTY, METADATA_KEY_RBDS_PTY);
    196         NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_RT, METADATA_KEY_RDS_RT);
    197         NATIVE_KEY_MAPPING.put(NATIVE_KEY_TITLE, METADATA_KEY_TITLE);
    198         NATIVE_KEY_MAPPING.put(NATIVE_KEY_ARTIST, METADATA_KEY_ARTIST);
    199         NATIVE_KEY_MAPPING.put(NATIVE_KEY_ALBUM, METADATA_KEY_ALBUM);
    200         NATIVE_KEY_MAPPING.put(NATIVE_KEY_GENRE, METADATA_KEY_GENRE);
    201         NATIVE_KEY_MAPPING.put(NATIVE_KEY_ICON, METADATA_KEY_ICON);
    202         NATIVE_KEY_MAPPING.put(NATIVE_KEY_ART, METADATA_KEY_ART);
    203         NATIVE_KEY_MAPPING.put(NATIVE_KEY_CLOCK, METADATA_KEY_CLOCK);
    204     }
    205 
    206     /**
    207      * Provides a Clock that can be used to describe time as provided by the Radio.
    208      *
    209      * The clock is defined by the seconds since epoch at the UTC + 0 timezone
    210      * and timezone offset from UTC + 0 represented in number of minutes.
    211      *
    212      * @hide
    213      */
    214     @SystemApi
    215     public static final class Clock implements Parcelable {
    216         private final long mUtcEpochSeconds;
    217         private final int mTimezoneOffsetMinutes;
    218 
    219         public int describeContents() {
    220             return 0;
    221         }
    222 
    223         public void writeToParcel(Parcel out, int flags) {
    224             out.writeLong(mUtcEpochSeconds);
    225             out.writeInt(mTimezoneOffsetMinutes);
    226         }
    227 
    228         public static final Parcelable.Creator<Clock> CREATOR
    229                 = new Parcelable.Creator<Clock>() {
    230             public Clock createFromParcel(Parcel in) {
    231                 return new Clock(in);
    232             }
    233 
    234             public Clock[] newArray(int size) {
    235                 return new Clock[size];
    236             }
    237         };
    238 
    239         public Clock(long utcEpochSeconds, int timezoneOffsetMinutes) {
    240             mUtcEpochSeconds = utcEpochSeconds;
    241             mTimezoneOffsetMinutes = timezoneOffsetMinutes;
    242         }
    243 
    244         private Clock(Parcel in) {
    245             mUtcEpochSeconds = in.readLong();
    246             mTimezoneOffsetMinutes = in.readInt();
    247         }
    248 
    249         public long getUtcEpochSeconds() {
    250             return mUtcEpochSeconds;
    251         }
    252 
    253         public int getTimezoneOffsetMinutes() {
    254             return mTimezoneOffsetMinutes;
    255         }
    256     }
    257 
    258     private final Bundle mBundle;
    259 
    260     RadioMetadata() {
    261         mBundle = new Bundle();
    262     }
    263 
    264     private RadioMetadata(Bundle bundle) {
    265         mBundle = new Bundle(bundle);
    266     }
    267 
    268     private RadioMetadata(Parcel in) {
    269         mBundle = in.readBundle();
    270     }
    271 
    272     @Override
    273     public String toString() {
    274         StringBuilder sb = new StringBuilder("RadioMetadata[");
    275 
    276         final String removePrefix = "android.hardware.radio.metadata";
    277 
    278         boolean first = true;
    279         for (String key : mBundle.keySet()) {
    280             if (first) first = false;
    281             else sb.append(", ");
    282 
    283             String keyDisp = key;
    284             if (key.startsWith(removePrefix)) keyDisp = key.substring(removePrefix.length());
    285 
    286             sb.append(keyDisp);
    287             sb.append('=');
    288             sb.append(mBundle.get(key));
    289         }
    290 
    291         sb.append("]");
    292         return sb.toString();
    293     }
    294 
    295     /**
    296      * Returns {@code true} if the given key is contained in the meta data
    297      *
    298      * @param key a String key
    299      * @return {@code true} if the key exists in this meta data, {@code false} otherwise
    300      */
    301     public boolean containsKey(String key) {
    302         return mBundle.containsKey(key);
    303     }
    304 
    305     /**
    306      * Returns the text value associated with the given key as a String, or null
    307      * if the key is not found in the meta data.
    308      *
    309      * @param key The key the value is stored under
    310      * @return a String value, or null
    311      */
    312     public String getString(String key) {
    313         return mBundle.getString(key);
    314     }
    315 
    316     private static void putInt(Bundle bundle, String key, int value) {
    317         int type = METADATA_KEYS_TYPE.getOrDefault(key, METADATA_TYPE_INVALID);
    318         if (type != METADATA_TYPE_INT && type != METADATA_TYPE_BITMAP) {
    319             throw new IllegalArgumentException("The " + key + " key cannot be used to put an int");
    320         }
    321         bundle.putInt(key, value);
    322     }
    323 
    324     /**
    325      * Returns the value associated with the given key,
    326      * or 0 if the key is not found in the meta data.
    327      *
    328      * @param key The key the value is stored under
    329      * @return an int value
    330      */
    331     public int getInt(String key) {
    332         return mBundle.getInt(key, 0);
    333     }
    334 
    335     /**
    336      * Returns a {@link Bitmap} for the given key or null if the key is not found in the meta data.
    337      *
    338      * @param key The key the value is stored under
    339      * @return a {@link Bitmap} or null
    340      * @deprecated Use getBitmapId(String) instead
    341      */
    342     @Deprecated
    343     public Bitmap getBitmap(String key) {
    344         Bitmap bmp = null;
    345         try {
    346             bmp = mBundle.getParcelable(key);
    347         } catch (Exception e) {
    348             // ignore, value was not a bitmap
    349             Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
    350         }
    351         return bmp;
    352     }
    353 
    354     /**
    355      * Retrieves an identifier for a bitmap.
    356      *
    357      * The format of an identifier is opaque to the application,
    358      * with a special case of value 0 being invalid.
    359      * An identifier for a given image-tuner pair is unique, so an application
    360      * may cache images and determine if there is a necessity to fetch them
    361      * again - if identifier changes, it means the image has changed.
    362      *
    363      * Only bitmap keys may be used with this method:
    364      * <ul>
    365      * <li>{@link #METADATA_KEY_ICON}</li>
    366      * <li>{@link #METADATA_KEY_ART}</li>
    367      * </ul>
    368      *
    369      * @param key The key the value is stored under.
    370      * @return a bitmap identifier or 0 if it's missing.
    371      * @hide This API is not thoroughly elaborated yet
    372      */
    373     public int getBitmapId(@NonNull String key) {
    374         if (!METADATA_KEY_ICON.equals(key) && !METADATA_KEY_ART.equals(key)) return 0;
    375         return getInt(key);
    376     }
    377 
    378     public Clock getClock(String key) {
    379         Clock clock = null;
    380         try {
    381             clock = mBundle.getParcelable(key);
    382         } catch (Exception e) {
    383             // ignore, value was not a clock.
    384             Log.w(TAG, "Failed to retrieve a key as Clock.", e);
    385         }
    386         return clock;
    387     }
    388 
    389     @Override
    390     public int describeContents() {
    391         return 0;
    392     }
    393 
    394     @Override
    395     public void writeToParcel(Parcel dest, int flags) {
    396         dest.writeBundle(mBundle);
    397     }
    398 
    399     /**
    400      * Returns the number of fields in this meta data.
    401      *
    402      * @return the number of fields in the meta data.
    403      */
    404     public int size() {
    405         return mBundle.size();
    406     }
    407 
    408     /**
    409      * Returns a Set containing the Strings used as keys in this meta data.
    410      *
    411      * @return a Set of String keys
    412      */
    413     public Set<String> keySet() {
    414         return mBundle.keySet();
    415     }
    416 
    417     /**
    418      * Helper for getting the String key used by {@link RadioMetadata} from the
    419      * corrsponding native integer key.
    420      *
    421      * @param editorKey The key used by the editor
    422      * @return the key used by this class or null if no mapping exists
    423      * @hide
    424      */
    425     public static String getKeyFromNativeKey(int nativeKey) {
    426         return NATIVE_KEY_MAPPING.get(nativeKey, null);
    427     }
    428 
    429     public static final Parcelable.Creator<RadioMetadata> CREATOR =
    430             new Parcelable.Creator<RadioMetadata>() {
    431                 @Override
    432                 public RadioMetadata createFromParcel(Parcel in) {
    433                     return new RadioMetadata(in);
    434                 }
    435 
    436                 @Override
    437                 public RadioMetadata[] newArray(int size) {
    438                     return new RadioMetadata[size];
    439                 }
    440             };
    441 
    442     /**
    443      * Use to build RadioMetadata objects.
    444      */
    445     public static final class Builder {
    446         private final Bundle mBundle;
    447 
    448         /**
    449          * Create an empty Builder. Any field that should be included in the
    450          * {@link RadioMetadata} must be added.
    451          */
    452         public Builder() {
    453             mBundle = new Bundle();
    454         }
    455 
    456         /**
    457          * Create a Builder using a {@link RadioMetadata} instance to set the
    458          * initial values. All fields in the source meta data will be included in
    459          * the new meta data. Fields can be overwritten by adding the same key.
    460          *
    461          * @param source
    462          */
    463         public Builder(RadioMetadata source) {
    464             mBundle = new Bundle(source.mBundle);
    465         }
    466 
    467         /**
    468          * Create a Builder using a {@link RadioMetadata} instance to set
    469          * initial values, but replace bitmaps with a scaled down copy if they
    470          * are larger than maxBitmapSize.
    471          *
    472          * @param source The original meta data to copy.
    473          * @param maxBitmapSize The maximum height/width for bitmaps contained
    474          *            in the meta data.
    475          * @hide
    476          */
    477         public Builder(RadioMetadata source, int maxBitmapSize) {
    478             this(source);
    479             for (String key : mBundle.keySet()) {
    480                 Object value = mBundle.get(key);
    481                 if (value != null && value instanceof Bitmap) {
    482                     Bitmap bmp = (Bitmap) value;
    483                     if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) {
    484                         putBitmap(key, scaleBitmap(bmp, maxBitmapSize));
    485                     }
    486                 }
    487             }
    488         }
    489 
    490         /**
    491          * Put a String value into the meta data. Custom keys may be used, but if
    492          * the METADATA_KEYs defined in this class are used they may only be one
    493          * of the following:
    494          * <ul>
    495          * <li>{@link #METADATA_KEY_RDS_PS}</li>
    496          * <li>{@link #METADATA_KEY_RDS_RT}</li>
    497          * <li>{@link #METADATA_KEY_TITLE}</li>
    498          * <li>{@link #METADATA_KEY_ARTIST}</li>
    499          * <li>{@link #METADATA_KEY_ALBUM}</li>
    500          * <li>{@link #METADATA_KEY_GENRE}</li>
    501          * </ul>
    502          *
    503          * @param key The key for referencing this value
    504          * @param value The String value to store
    505          * @return the same Builder instance
    506          */
    507         public Builder putString(String key, String value) {
    508             if (!METADATA_KEYS_TYPE.containsKey(key) ||
    509                     METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
    510                 throw new IllegalArgumentException("The " + key
    511                         + " key cannot be used to put a String");
    512             }
    513             mBundle.putString(key, value);
    514             return this;
    515         }
    516 
    517         /**
    518          * Put an int value into the meta data. Custom keys may be used, but if
    519          * the METADATA_KEYs defined in this class are used they may only be one
    520          * of the following:
    521          * <ul>
    522          * <li>{@link #METADATA_KEY_RDS_PI}</li>
    523          * <li>{@link #METADATA_KEY_RDS_PTY}</li>
    524          * <li>{@link #METADATA_KEY_RBDS_PTY}</li>
    525          * </ul>
    526          * or any bitmap represented by its identifier.
    527          *
    528          * @param key The key for referencing this value
    529          * @param value The int value to store
    530          * @return the same Builder instance
    531          */
    532         public Builder putInt(String key, int value) {
    533             RadioMetadata.putInt(mBundle, key, value);
    534             return this;
    535         }
    536 
    537         /**
    538          * Put a {@link Bitmap} into the meta data. Custom keys may be used, but
    539          * if the METADATA_KEYs defined in this class are used they may only be
    540          * one of the following:
    541          * <ul>
    542          * <li>{@link #METADATA_KEY_ICON}</li>
    543          * <li>{@link #METADATA_KEY_ART}</li>
    544          * </ul>
    545          * <p>
    546          *
    547          * @param key The key for referencing this value
    548          * @param value The Bitmap to store
    549          * @return the same Builder instance
    550          */
    551         public Builder putBitmap(String key, Bitmap value) {
    552             if (!METADATA_KEYS_TYPE.containsKey(key) ||
    553                     METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
    554                 throw new IllegalArgumentException("The " + key
    555                         + " key cannot be used to put a Bitmap");
    556             }
    557             mBundle.putParcelable(key, value);
    558             return this;
    559         }
    560 
    561         /**
    562          * Put a {@link RadioMetadata.Clock} into the meta data. Custom keys may be used, but if the
    563          * METADATA_KEYs defined in this class are used they may only be one of the following:
    564          * <ul>
    565          * <li>{@link #MEADATA_KEY_CLOCK}</li>
    566          * </ul>
    567          *
    568          * @param utcSecondsSinceEpoch Number of seconds since epoch for UTC + 0 timezone.
    569          * @param timezoneOffsetInMinutes Offset of timezone from UTC + 0 in minutes.
    570          * @return the same Builder instance.
    571          */
    572         public Builder putClock(String key, long utcSecondsSinceEpoch, int timezoneOffsetMinutes) {
    573             if (!METADATA_KEYS_TYPE.containsKey(key) ||
    574                     METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_CLOCK) {
    575                 throw new IllegalArgumentException("The " + key
    576                     + " key cannot be used to put a RadioMetadata.Clock.");
    577             }
    578             mBundle.putParcelable(key, new Clock(utcSecondsSinceEpoch, timezoneOffsetMinutes));
    579             return this;
    580         }
    581 
    582         /**
    583          * Creates a {@link RadioMetadata} instance with the specified fields.
    584          *
    585          * @return a new {@link RadioMetadata} object
    586          */
    587         public RadioMetadata build() {
    588             return new RadioMetadata(mBundle);
    589         }
    590 
    591         private Bitmap scaleBitmap(Bitmap bmp, int maxSize) {
    592             float maxSizeF = maxSize;
    593             float widthScale = maxSizeF / bmp.getWidth();
    594             float heightScale = maxSizeF / bmp.getHeight();
    595             float scale = Math.min(widthScale, heightScale);
    596             int height = (int) (bmp.getHeight() * scale);
    597             int width = (int) (bmp.getWidth() * scale);
    598             return Bitmap.createScaledBitmap(bmp, width, height, true);
    599         }
    600     }
    601 
    602     int putIntFromNative(int nativeKey, int value) {
    603         String key = getKeyFromNativeKey(nativeKey);
    604         try {
    605             putInt(mBundle, key, value);
    606             return 0;
    607         } catch (IllegalArgumentException ex) {
    608             return -1;
    609         }
    610     }
    611 
    612     int putStringFromNative(int nativeKey, String value) {
    613         String key = getKeyFromNativeKey(nativeKey);
    614         if (!METADATA_KEYS_TYPE.containsKey(key) ||
    615                 METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
    616             return -1;
    617         }
    618         mBundle.putString(key, value);
    619         return 0;
    620     }
    621 
    622     int putBitmapFromNative(int nativeKey, byte[] value) {
    623         String key = getKeyFromNativeKey(nativeKey);
    624         if (!METADATA_KEYS_TYPE.containsKey(key) ||
    625                 METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
    626             return -1;
    627         }
    628         Bitmap bmp = null;
    629         try {
    630             bmp = BitmapFactory.decodeByteArray(value, 0, value.length);
    631             if (bmp != null) {
    632                 mBundle.putParcelable(key, bmp);
    633                 return 0;
    634             }
    635         } catch (Exception e) {
    636         }
    637         return -1;
    638     }
    639 
    640     int putClockFromNative(int nativeKey, long utcEpochSeconds, int timezoneOffsetInMinutes) {
    641         String key = getKeyFromNativeKey(nativeKey);
    642         if (!METADATA_KEYS_TYPE.containsKey(key) ||
    643                 METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_CLOCK) {
    644               return -1;
    645         }
    646         mBundle.putParcelable(key, new RadioMetadata.Clock(
    647             utcEpochSeconds, timezoneOffsetInMinutes));
    648         return 0;
    649     }
    650 }
    651