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.content.ContentResolver;
     21 import android.graphics.Bitmap;
     22 import android.graphics.BitmapFactory;
     23 import android.net.Uri;
     24 import android.os.Bundle;
     25 import android.os.Parcel;
     26 import android.os.Parcelable;
     27 import android.text.TextUtils;
     28 import android.util.ArrayMap;
     29 import android.util.Log;
     30 import android.util.SparseArray;
     31 
     32 import java.util.ArrayList;
     33 import java.util.Set;
     34 
     35 /**
     36  * Contains meta data about a radio program such as station name, song title, artist etc...
     37  * @hide
     38  */
     39 @SystemApi
     40 public final class RadioMetadata implements Parcelable {
     41     private static final String TAG = "RadioMetadata";
     42 
     43     /**
     44      * The RDS Program Information.
     45      */
     46     public static final String METADATA_KEY_RDS_PI = "android.hardware.radio.metadata.RDS_PI";
     47 
     48     /**
     49      * The RDS Program Service.
     50      */
     51     public static final String METADATA_KEY_RDS_PS = "android.hardware.radio.metadata.RDS_PS";
     52 
     53     /**
     54      * The RDS PTY.
     55      */
     56     public static final String METADATA_KEY_RDS_PTY = "android.hardware.radio.metadata.RDS_PTY";
     57 
     58     /**
     59      * The RBDS PTY.
     60      */
     61     public static final String METADATA_KEY_RBDS_PTY = "android.hardware.radio.metadata.RBDS_PTY";
     62 
     63     /**
     64      * The RBDS Radio Text.
     65      */
     66     public static final String METADATA_KEY_RDS_RT = "android.hardware.radio.metadata.RDS_RT";
     67 
     68     /**
     69      * The song title.
     70      */
     71     public static final String METADATA_KEY_TITLE = "android.hardware.radio.metadata.TITLE";
     72 
     73     /**
     74      * The artist name.
     75      */
     76     public static final String METADATA_KEY_ARTIST = "android.hardware.radio.metadata.ARTIST";
     77 
     78     /**
     79      * The album name.
     80      */
     81     public static final String METADATA_KEY_ALBUM = "android.hardware.radio.metadata.ALBUM";
     82 
     83     /**
     84      * The music genre.
     85      */
     86     public static final String METADATA_KEY_GENRE = "android.hardware.radio.metadata.GENRE";
     87 
     88     /**
     89      * The radio station icon {@link Bitmap}.
     90      */
     91     public static final String METADATA_KEY_ICON = "android.hardware.radio.metadata.ICON";
     92 
     93     /**
     94      * The artwork for the song/album {@link Bitmap}.
     95      */
     96     public static final String METADATA_KEY_ART = "android.hardware.radio.metadata.ART";
     97 
     98     /**
     99      * The clock.
    100      */
    101     public static final String METADATA_KEY_CLOCK = "android.hardware.radio.metadata.CLOCK";
    102 
    103 
    104     private static final int METADATA_TYPE_INVALID = -1;
    105     private static final int METADATA_TYPE_INT = 0;
    106     private static final int METADATA_TYPE_TEXT = 1;
    107     private static final int METADATA_TYPE_BITMAP = 2;
    108     private static final int METADATA_TYPE_CLOCK = 3;
    109 
    110     private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
    111 
    112     static {
    113         METADATA_KEYS_TYPE = new ArrayMap<String, Integer>();
    114         METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PI, METADATA_TYPE_TEXT);
    115         METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PS, METADATA_TYPE_TEXT);
    116         METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PTY, METADATA_TYPE_INT);
    117         METADATA_KEYS_TYPE.put(METADATA_KEY_RBDS_PTY, METADATA_TYPE_INT);
    118         METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_RT, METADATA_TYPE_TEXT);
    119         METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
    120         METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
    121         METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
    122         METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
    123         METADATA_KEYS_TYPE.put(METADATA_KEY_ICON, METADATA_TYPE_BITMAP);
    124         METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
    125         METADATA_KEYS_TYPE.put(METADATA_KEY_CLOCK, METADATA_TYPE_CLOCK);
    126     }
    127 
    128     // keep in sync with: system/media/radio/include/system/radio_metadata.h
    129     private static final int NATIVE_KEY_INVALID     = -1;
    130     private static final int NATIVE_KEY_RDS_PI      = 0;
    131     private static final int NATIVE_KEY_RDS_PS      = 1;
    132     private static final int NATIVE_KEY_RDS_PTY     = 2;
    133     private static final int NATIVE_KEY_RBDS_PTY    = 3;
    134     private static final int NATIVE_KEY_RDS_RT      = 4;
    135     private static final int NATIVE_KEY_TITLE       = 5;
    136     private static final int NATIVE_KEY_ARTIST      = 6;
    137     private static final int NATIVE_KEY_ALBUM       = 7;
    138     private static final int NATIVE_KEY_GENRE       = 8;
    139     private static final int NATIVE_KEY_ICON        = 9;
    140     private static final int NATIVE_KEY_ART         = 10;
    141     private static final int NATIVE_KEY_CLOCK       = 11;
    142 
    143     private static final SparseArray<String> NATIVE_KEY_MAPPING;
    144 
    145     static {
    146         NATIVE_KEY_MAPPING = new SparseArray<String>();
    147         NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PI, METADATA_KEY_RDS_PI);
    148         NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PS, METADATA_KEY_RDS_PS);
    149         NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PTY, METADATA_KEY_RDS_PTY);
    150         NATIVE_KEY_MAPPING.put(NATIVE_KEY_RBDS_PTY, METADATA_KEY_RBDS_PTY);
    151         NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_RT, METADATA_KEY_RDS_RT);
    152         NATIVE_KEY_MAPPING.put(NATIVE_KEY_TITLE, METADATA_KEY_TITLE);
    153         NATIVE_KEY_MAPPING.put(NATIVE_KEY_ARTIST, METADATA_KEY_ARTIST);
    154         NATIVE_KEY_MAPPING.put(NATIVE_KEY_ALBUM, METADATA_KEY_ALBUM);
    155         NATIVE_KEY_MAPPING.put(NATIVE_KEY_GENRE, METADATA_KEY_GENRE);
    156         NATIVE_KEY_MAPPING.put(NATIVE_KEY_ICON, METADATA_KEY_ICON);
    157         NATIVE_KEY_MAPPING.put(NATIVE_KEY_ART, METADATA_KEY_ART);
    158         NATIVE_KEY_MAPPING.put(NATIVE_KEY_CLOCK, METADATA_KEY_CLOCK);
    159     }
    160 
    161     /**
    162      * Provides a Clock that can be used to describe time as provided by the Radio.
    163      *
    164      * The clock is defined by the seconds since epoch at the UTC + 0 timezone
    165      * and timezone offset from UTC + 0 represented in number of minutes.
    166      *
    167      * @hide
    168      */
    169     @SystemApi
    170     public static final class Clock implements Parcelable {
    171         private final long mUtcEpochSeconds;
    172         private final int mTimezoneOffsetMinutes;
    173 
    174         public int describeContents() {
    175             return 0;
    176         }
    177 
    178         public void writeToParcel(Parcel out, int flags) {
    179             out.writeLong(mUtcEpochSeconds);
    180             out.writeInt(mTimezoneOffsetMinutes);
    181         }
    182 
    183         public static final Parcelable.Creator<Clock> CREATOR
    184                 = new Parcelable.Creator<Clock>() {
    185             public Clock createFromParcel(Parcel in) {
    186                 return new Clock(in);
    187             }
    188 
    189             public Clock[] newArray(int size) {
    190                 return new Clock[size];
    191             }
    192         };
    193 
    194         public Clock(long utcEpochSeconds, int timezoneOffsetMinutes) {
    195             mUtcEpochSeconds = utcEpochSeconds;
    196             mTimezoneOffsetMinutes = timezoneOffsetMinutes;
    197         }
    198 
    199         private Clock(Parcel in) {
    200             mUtcEpochSeconds = in.readLong();
    201             mTimezoneOffsetMinutes = in.readInt();
    202         }
    203 
    204         public long getUtcEpochSeconds() {
    205             return mUtcEpochSeconds;
    206         }
    207 
    208         public int getTimezoneOffsetMinutes() {
    209             return mTimezoneOffsetMinutes;
    210         }
    211     }
    212 
    213     private final Bundle mBundle;
    214 
    215     RadioMetadata() {
    216         mBundle = new Bundle();
    217     }
    218 
    219     private RadioMetadata(Bundle bundle) {
    220         mBundle = new Bundle(bundle);
    221     }
    222 
    223     private RadioMetadata(Parcel in) {
    224         mBundle = in.readBundle();
    225     }
    226 
    227     /**
    228      * Returns {@code true} if the given key is contained in the meta data
    229      *
    230      * @param key a String key
    231      * @return {@code true} if the key exists in this meta data, {@code false} otherwise
    232      */
    233     public boolean containsKey(String key) {
    234         return mBundle.containsKey(key);
    235     }
    236 
    237     /**
    238      * Returns the text value associated with the given key as a String, or null
    239      * if the key is not found in the meta data.
    240      *
    241      * @param key The key the value is stored under
    242      * @return a String value, or null
    243      */
    244     public String getString(String key) {
    245         return mBundle.getString(key);
    246     }
    247 
    248     /**
    249      * Returns the value associated with the given key,
    250      * or 0 if the key is not found in the meta data.
    251      *
    252      * @param key The key the value is stored under
    253      * @return an int value
    254      */
    255     public int getInt(String key) {
    256         return mBundle.getInt(key, 0);
    257     }
    258 
    259     /**
    260      * Returns a {@link Bitmap} for the given key or null if the key is not found in the meta data.
    261      *
    262      * @param key The key the value is stored under
    263      * @return a {@link Bitmap} or null
    264      */
    265     public Bitmap getBitmap(String key) {
    266         Bitmap bmp = null;
    267         try {
    268             bmp = mBundle.getParcelable(key);
    269         } catch (Exception e) {
    270             // ignore, value was not a bitmap
    271             Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
    272         }
    273         return bmp;
    274     }
    275 
    276     public Clock getClock(String key) {
    277         Clock clock = null;
    278         try {
    279             clock = mBundle.getParcelable(key);
    280         } catch (Exception e) {
    281             // ignore, value was not a clock.
    282             Log.w(TAG, "Failed to retrieve a key as Clock.", e);
    283         }
    284         return clock;
    285     }
    286 
    287     @Override
    288     public int describeContents() {
    289         return 0;
    290     }
    291 
    292     @Override
    293     public void writeToParcel(Parcel dest, int flags) {
    294         dest.writeBundle(mBundle);
    295     }
    296 
    297     /**
    298      * Returns the number of fields in this meta data.
    299      *
    300      * @return the number of fields in the meta data.
    301      */
    302     public int size() {
    303         return mBundle.size();
    304     }
    305 
    306     /**
    307      * Returns a Set containing the Strings used as keys in this meta data.
    308      *
    309      * @return a Set of String keys
    310      */
    311     public Set<String> keySet() {
    312         return mBundle.keySet();
    313     }
    314 
    315     /**
    316      * Helper for getting the String key used by {@link RadioMetadata} from the
    317      * corrsponding native integer key.
    318      *
    319      * @param editorKey The key used by the editor
    320      * @return the key used by this class or null if no mapping exists
    321      * @hide
    322      */
    323     public static String getKeyFromNativeKey(int nativeKey) {
    324         return NATIVE_KEY_MAPPING.get(nativeKey, null);
    325     }
    326 
    327     public static final Parcelable.Creator<RadioMetadata> CREATOR =
    328             new Parcelable.Creator<RadioMetadata>() {
    329                 @Override
    330                 public RadioMetadata createFromParcel(Parcel in) {
    331                     return new RadioMetadata(in);
    332                 }
    333 
    334                 @Override
    335                 public RadioMetadata[] newArray(int size) {
    336                     return new RadioMetadata[size];
    337                 }
    338             };
    339 
    340     /**
    341      * Use to build RadioMetadata objects.
    342      */
    343     public static final class Builder {
    344         private final Bundle mBundle;
    345 
    346         /**
    347          * Create an empty Builder. Any field that should be included in the
    348          * {@link RadioMetadata} must be added.
    349          */
    350         public Builder() {
    351             mBundle = new Bundle();
    352         }
    353 
    354         /**
    355          * Create a Builder using a {@link RadioMetadata} instance to set the
    356          * initial values. All fields in the source meta data will be included in
    357          * the new meta data. Fields can be overwritten by adding the same key.
    358          *
    359          * @param source
    360          */
    361         public Builder(RadioMetadata source) {
    362             mBundle = new Bundle(source.mBundle);
    363         }
    364 
    365         /**
    366          * Create a Builder using a {@link RadioMetadata} instance to set
    367          * initial values, but replace bitmaps with a scaled down copy if they
    368          * are larger than maxBitmapSize.
    369          *
    370          * @param source The original meta data to copy.
    371          * @param maxBitmapSize The maximum height/width for bitmaps contained
    372          *            in the meta data.
    373          * @hide
    374          */
    375         public Builder(RadioMetadata source, int maxBitmapSize) {
    376             this(source);
    377             for (String key : mBundle.keySet()) {
    378                 Object value = mBundle.get(key);
    379                 if (value != null && value instanceof Bitmap) {
    380                     Bitmap bmp = (Bitmap) value;
    381                     if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) {
    382                         putBitmap(key, scaleBitmap(bmp, maxBitmapSize));
    383                     }
    384                 }
    385             }
    386         }
    387 
    388         /**
    389          * Put a String value into the meta data. Custom keys may be used, but if
    390          * the METADATA_KEYs defined in this class are used they may only be one
    391          * of the following:
    392          * <ul>
    393          * <li>{@link #METADATA_KEY_RDS_PI}</li>
    394          * <li>{@link #METADATA_KEY_RDS_PS}</li>
    395          * <li>{@link #METADATA_KEY_RDS_RT}</li>
    396          * <li>{@link #METADATA_KEY_TITLE}</li>
    397          * <li>{@link #METADATA_KEY_ARTIST}</li>
    398          * <li>{@link #METADATA_KEY_ALBUM}</li>
    399          * <li>{@link #METADATA_KEY_GENRE}</li>
    400          * </ul>
    401          *
    402          * @param key The key for referencing this value
    403          * @param value The String value to store
    404          * @return the same Builder instance
    405          */
    406         public Builder putString(String key, String value) {
    407             if (!METADATA_KEYS_TYPE.containsKey(key) ||
    408                     METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
    409                 throw new IllegalArgumentException("The " + key
    410                         + " key cannot be used to put a String");
    411             }
    412             mBundle.putString(key, value);
    413             return this;
    414         }
    415 
    416         /**
    417          * Put an int value into the meta data. Custom keys may be used, but if
    418          * the METADATA_KEYs defined in this class are used they may only be one
    419          * of the following:
    420          * <ul>
    421          * <li>{@link #METADATA_KEY_RDS_PTY}</li>
    422          * <li>{@link #METADATA_KEY_RBDS_PTY}</li>
    423          * </ul>
    424          *
    425          * @param key The key for referencing this value
    426          * @param value The int value to store
    427          * @return the same Builder instance
    428          */
    429         public Builder putInt(String key, int value) {
    430             if (!METADATA_KEYS_TYPE.containsKey(key) ||
    431                     METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_INT) {
    432                 throw new IllegalArgumentException("The " + key
    433                         + " key cannot be used to put a long");
    434             }
    435             mBundle.putInt(key, value);
    436             return this;
    437         }
    438 
    439         /**
    440          * Put a {@link Bitmap} into the meta data. Custom keys may be used, but
    441          * if the METADATA_KEYs defined in this class are used they may only be
    442          * one of the following:
    443          * <ul>
    444          * <li>{@link #METADATA_KEY_ICON}</li>
    445          * <li>{@link #METADATA_KEY_ART}</li>
    446          * </ul>
    447          * <p>
    448          *
    449          * @param key The key for referencing this value
    450          * @param value The Bitmap to store
    451          * @return the same Builder instance
    452          */
    453         public Builder putBitmap(String key, Bitmap value) {
    454             if (!METADATA_KEYS_TYPE.containsKey(key) ||
    455                     METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
    456                 throw new IllegalArgumentException("The " + key
    457                         + " key cannot be used to put a Bitmap");
    458             }
    459             mBundle.putParcelable(key, value);
    460             return this;
    461         }
    462 
    463         /**
    464          * Put a {@link RadioMetadata.Clock} into the meta data. Custom keys may be used, but if the
    465          * METADATA_KEYs defined in this class are used they may only be one of the following:
    466          * <ul>
    467          * <li>{@link #MEADATA_KEY_CLOCK}</li>
    468          * </ul>
    469          *
    470          * @param utcSecondsSinceEpoch Number of seconds since epoch for UTC + 0 timezone.
    471          * @param timezoneOffsetInMinutes Offset of timezone from UTC + 0 in minutes.
    472          * @return the same Builder instance.
    473          */
    474         public Builder putClock(String key, long utcSecondsSinceEpoch, int timezoneOffsetMinutes) {
    475             if (!METADATA_KEYS_TYPE.containsKey(key) ||
    476                     METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_CLOCK) {
    477                 throw new IllegalArgumentException("The " + key
    478                     + " key cannot be used to put a RadioMetadata.Clock.");
    479             }
    480             mBundle.putParcelable(key, new Clock(utcSecondsSinceEpoch, timezoneOffsetMinutes));
    481             return this;
    482         }
    483 
    484         /**
    485          * Creates a {@link RadioMetadata} instance with the specified fields.
    486          *
    487          * @return a new {@link RadioMetadata} object
    488          */
    489         public RadioMetadata build() {
    490             return new RadioMetadata(mBundle);
    491         }
    492 
    493         private Bitmap scaleBitmap(Bitmap bmp, int maxSize) {
    494             float maxSizeF = maxSize;
    495             float widthScale = maxSizeF / bmp.getWidth();
    496             float heightScale = maxSizeF / bmp.getHeight();
    497             float scale = Math.min(widthScale, heightScale);
    498             int height = (int) (bmp.getHeight() * scale);
    499             int width = (int) (bmp.getWidth() * scale);
    500             return Bitmap.createScaledBitmap(bmp, width, height, true);
    501         }
    502     }
    503 
    504     int putIntFromNative(int nativeKey, int value) {
    505         String key = getKeyFromNativeKey(nativeKey);
    506         if (!METADATA_KEYS_TYPE.containsKey(key) ||
    507                 METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_INT) {
    508             return -1;
    509         }
    510         mBundle.putInt(key, value);
    511         return 0;
    512     }
    513 
    514     int putStringFromNative(int nativeKey, String value) {
    515         String key = getKeyFromNativeKey(nativeKey);
    516         if (!METADATA_KEYS_TYPE.containsKey(key) ||
    517                 METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
    518             return -1;
    519         }
    520         mBundle.putString(key, value);
    521         return 0;
    522     }
    523 
    524     int putBitmapFromNative(int nativeKey, byte[] value) {
    525         String key = getKeyFromNativeKey(nativeKey);
    526         if (!METADATA_KEYS_TYPE.containsKey(key) ||
    527                 METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
    528             return -1;
    529         }
    530         Bitmap bmp = null;
    531         try {
    532             bmp = BitmapFactory.decodeByteArray(value, 0, value.length);
    533             if (bmp != null) {
    534                 mBundle.putParcelable(key, bmp);
    535                 return 0;
    536             }
    537         } catch (Exception e) {
    538         }
    539         return -1;
    540     }
    541 
    542     int putClockFromNative(int nativeKey, long utcEpochSeconds, int timezoneOffsetInMinutes) {
    543         Log.d(TAG, "putClockFromNative()");
    544         String key = getKeyFromNativeKey(nativeKey);
    545         if (!METADATA_KEYS_TYPE.containsKey(key) ||
    546                 METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_CLOCK) {
    547               return -1;
    548         }
    549         mBundle.putParcelable(key, new RadioMetadata.Clock(
    550             utcEpochSeconds, timezoneOffsetInMinutes));
    551         return 0;
    552     }
    553 }
    554