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>();
METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PI, METADATA_TYPE_INT)152         METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PI, METADATA_TYPE_INT);
METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PS, METADATA_TYPE_TEXT)153         METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PS, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PTY, METADATA_TYPE_INT)154         METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PTY, METADATA_TYPE_INT);
METADATA_KEYS_TYPE.put(METADATA_KEY_RBDS_PTY, METADATA_TYPE_INT)155         METADATA_KEYS_TYPE.put(METADATA_KEY_RBDS_PTY, METADATA_TYPE_INT);
METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_RT, METADATA_TYPE_TEXT)156         METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_RT, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT)157         METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT)158         METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT)159         METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT)160         METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_ICON, METADATA_TYPE_BITMAP)161         METADATA_KEYS_TYPE.put(METADATA_KEY_ICON, METADATA_TYPE_BITMAP);
METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP)162         METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
METADATA_KEYS_TYPE.put(METADATA_KEY_CLOCK, METADATA_TYPE_CLOCK)163         METADATA_KEYS_TYPE.put(METADATA_KEY_CLOCK, METADATA_TYPE_CLOCK);
METADATA_KEYS_TYPE.put(METADATA_KEY_PROGRAM_NAME, METADATA_TYPE_TEXT)164         METADATA_KEYS_TYPE.put(METADATA_KEY_PROGRAM_NAME, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_ENSEMBLE_NAME, METADATA_TYPE_TEXT)165         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)166         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)167         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)168         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)169         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)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>();
NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PI, METADATA_KEY_RDS_PI)192         NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PI, METADATA_KEY_RDS_PI);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PS, METADATA_KEY_RDS_PS)193         NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PS, METADATA_KEY_RDS_PS);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PTY, METADATA_KEY_RDS_PTY)194         NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PTY, METADATA_KEY_RDS_PTY);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_RBDS_PTY, METADATA_KEY_RBDS_PTY)195         NATIVE_KEY_MAPPING.put(NATIVE_KEY_RBDS_PTY, METADATA_KEY_RBDS_PTY);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_RT, METADATA_KEY_RDS_RT)196         NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_RT, METADATA_KEY_RDS_RT);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_TITLE, METADATA_KEY_TITLE)197         NATIVE_KEY_MAPPING.put(NATIVE_KEY_TITLE, METADATA_KEY_TITLE);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_ARTIST, METADATA_KEY_ARTIST)198         NATIVE_KEY_MAPPING.put(NATIVE_KEY_ARTIST, METADATA_KEY_ARTIST);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_ALBUM, METADATA_KEY_ALBUM)199         NATIVE_KEY_MAPPING.put(NATIVE_KEY_ALBUM, METADATA_KEY_ALBUM);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_GENRE, METADATA_KEY_GENRE)200         NATIVE_KEY_MAPPING.put(NATIVE_KEY_GENRE, METADATA_KEY_GENRE);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_ICON, METADATA_KEY_ICON)201         NATIVE_KEY_MAPPING.put(NATIVE_KEY_ICON, METADATA_KEY_ICON);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_ART, METADATA_KEY_ART)202         NATIVE_KEY_MAPPING.put(NATIVE_KEY_ART, METADATA_KEY_ART);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_CLOCK, METADATA_KEY_CLOCK)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 
describeContents()219         public int describeContents() {
220             return 0;
221         }
222 
writeToParcel(Parcel out, int flags)223         public void writeToParcel(Parcel out, int flags) {
224             out.writeLong(mUtcEpochSeconds);
225             out.writeInt(mTimezoneOffsetMinutes);
226         }
227 
228         public static final @android.annotation.NonNull 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 
Clock(long utcEpochSeconds, int timezoneOffsetMinutes)239         public Clock(long utcEpochSeconds, int timezoneOffsetMinutes) {
240             mUtcEpochSeconds = utcEpochSeconds;
241             mTimezoneOffsetMinutes = timezoneOffsetMinutes;
242         }
243 
Clock(Parcel in)244         private Clock(Parcel in) {
245             mUtcEpochSeconds = in.readLong();
246             mTimezoneOffsetMinutes = in.readInt();
247         }
248 
getUtcEpochSeconds()249         public long getUtcEpochSeconds() {
250             return mUtcEpochSeconds;
251         }
252 
getTimezoneOffsetMinutes()253         public int getTimezoneOffsetMinutes() {
254             return mTimezoneOffsetMinutes;
255         }
256     }
257 
258     private final Bundle mBundle;
259 
RadioMetadata()260     RadioMetadata() {
261         mBundle = new Bundle();
262     }
263 
RadioMetadata(Bundle bundle)264     private RadioMetadata(Bundle bundle) {
265         mBundle = new Bundle(bundle);
266     }
267 
RadioMetadata(Parcel in)268     private RadioMetadata(Parcel in) {
269         mBundle = in.readBundle();
270     }
271 
272     @NonNull
273     @Override
toString()274     public String toString() {
275         StringBuilder sb = new StringBuilder("RadioMetadata[");
276 
277         final String removePrefix = "android.hardware.radio.metadata";
278 
279         boolean first = true;
280         for (String key : mBundle.keySet()) {
281             if (first) first = false;
282             else sb.append(", ");
283 
284             String keyDisp = key;
285             if (key.startsWith(removePrefix)) keyDisp = key.substring(removePrefix.length());
286 
287             sb.append(keyDisp);
288             sb.append('=');
289             sb.append(mBundle.get(key));
290         }
291 
292         sb.append("]");
293         return sb.toString();
294     }
295 
296     /**
297      * Returns {@code true} if the given key is contained in the meta data
298      *
299      * @param key a String key
300      * @return {@code true} if the key exists in this meta data, {@code false} otherwise
301      */
containsKey(String key)302     public boolean containsKey(String key) {
303         return mBundle.containsKey(key);
304     }
305 
306     /**
307      * Returns the text value associated with the given key as a String, or null
308      * if the key is not found in the meta data.
309      *
310      * @param key The key the value is stored under
311      * @return a String value, or null
312      */
getString(String key)313     public String getString(String key) {
314         return mBundle.getString(key);
315     }
316 
putInt(Bundle bundle, String key, int value)317     private static void putInt(Bundle bundle, String key, int value) {
318         int type = METADATA_KEYS_TYPE.getOrDefault(key, METADATA_TYPE_INVALID);
319         if (type != METADATA_TYPE_INT && type != METADATA_TYPE_BITMAP) {
320             throw new IllegalArgumentException("The " + key + " key cannot be used to put an int");
321         }
322         bundle.putInt(key, value);
323     }
324 
325     /**
326      * Returns the value associated with the given key,
327      * or 0 if the key is not found in the meta data.
328      *
329      * @param key The key the value is stored under
330      * @return an int value
331      */
getInt(String key)332     public int getInt(String key) {
333         return mBundle.getInt(key, 0);
334     }
335 
336     /**
337      * Returns a {@link Bitmap} for the given key or null if the key is not found in the meta data.
338      *
339      * @param key The key the value is stored under
340      * @return a {@link Bitmap} or null
341      * @deprecated Use getBitmapId(String) instead
342      */
343     @Deprecated
getBitmap(String key)344     public Bitmap getBitmap(String key) {
345         Bitmap bmp = null;
346         try {
347             bmp = mBundle.getParcelable(key);
348         } catch (Exception e) {
349             // ignore, value was not a bitmap
350             Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
351         }
352         return bmp;
353     }
354 
355     /**
356      * Retrieves an identifier for a bitmap.
357      *
358      * The format of an identifier is opaque to the application,
359      * with a special case of value 0 being invalid.
360      * An identifier for a given image-tuner pair is unique, so an application
361      * may cache images and determine if there is a necessity to fetch them
362      * again - if identifier changes, it means the image has changed.
363      *
364      * Only bitmap keys may be used with this method:
365      * <ul>
366      * <li>{@link #METADATA_KEY_ICON}</li>
367      * <li>{@link #METADATA_KEY_ART}</li>
368      * </ul>
369      *
370      * @param key The key the value is stored under.
371      * @return a bitmap identifier or 0 if it's missing.
372      * @hide This API is not thoroughly elaborated yet
373      */
getBitmapId(@onNull String key)374     public int getBitmapId(@NonNull String key) {
375         if (!METADATA_KEY_ICON.equals(key) && !METADATA_KEY_ART.equals(key)) return 0;
376         return getInt(key);
377     }
378 
getClock(String key)379     public Clock getClock(String key) {
380         Clock clock = null;
381         try {
382             clock = mBundle.getParcelable(key);
383         } catch (Exception e) {
384             // ignore, value was not a clock.
385             Log.w(TAG, "Failed to retrieve a key as Clock.", e);
386         }
387         return clock;
388     }
389 
390     @Override
describeContents()391     public int describeContents() {
392         return 0;
393     }
394 
395     @Override
writeToParcel(Parcel dest, int flags)396     public void writeToParcel(Parcel dest, int flags) {
397         dest.writeBundle(mBundle);
398     }
399 
400     /**
401      * Returns the number of fields in this meta data.
402      *
403      * @return the number of fields in the meta data.
404      */
size()405     public int size() {
406         return mBundle.size();
407     }
408 
409     /**
410      * Returns a Set containing the Strings used as keys in this meta data.
411      *
412      * @return a Set of String keys
413      */
keySet()414     public Set<String> keySet() {
415         return mBundle.keySet();
416     }
417 
418     /**
419      * Helper for getting the String key used by {@link RadioMetadata} from the
420      * corrsponding native integer key.
421      *
422      * @param editorKey The key used by the editor
423      * @return the key used by this class or null if no mapping exists
424      * @hide
425      */
getKeyFromNativeKey(int nativeKey)426     public static String getKeyFromNativeKey(int nativeKey) {
427         return NATIVE_KEY_MAPPING.get(nativeKey, null);
428     }
429 
430     public static final @android.annotation.NonNull Parcelable.Creator<RadioMetadata> CREATOR =
431             new Parcelable.Creator<RadioMetadata>() {
432                 @Override
433                 public RadioMetadata createFromParcel(Parcel in) {
434                     return new RadioMetadata(in);
435                 }
436 
437                 @Override
438                 public RadioMetadata[] newArray(int size) {
439                     return new RadioMetadata[size];
440                 }
441             };
442 
443     /**
444      * Use to build RadioMetadata objects.
445      */
446     public static final class Builder {
447         private final Bundle mBundle;
448 
449         /**
450          * Create an empty Builder. Any field that should be included in the
451          * {@link RadioMetadata} must be added.
452          */
Builder()453         public Builder() {
454             mBundle = new Bundle();
455         }
456 
457         /**
458          * Create a Builder using a {@link RadioMetadata} instance to set the
459          * initial values. All fields in the source meta data will be included in
460          * the new meta data. Fields can be overwritten by adding the same key.
461          *
462          * @param source
463          */
Builder(RadioMetadata source)464         public Builder(RadioMetadata source) {
465             mBundle = new Bundle(source.mBundle);
466         }
467 
468         /**
469          * Create a Builder using a {@link RadioMetadata} instance to set
470          * initial values, but replace bitmaps with a scaled down copy if they
471          * are larger than maxBitmapSize.
472          *
473          * @param source The original meta data to copy.
474          * @param maxBitmapSize The maximum height/width for bitmaps contained
475          *            in the meta data.
476          * @hide
477          */
Builder(RadioMetadata source, int maxBitmapSize)478         public Builder(RadioMetadata source, int maxBitmapSize) {
479             this(source);
480             for (String key : mBundle.keySet()) {
481                 Object value = mBundle.get(key);
482                 if (value != null && value instanceof Bitmap) {
483                     Bitmap bmp = (Bitmap) value;
484                     if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) {
485                         putBitmap(key, scaleBitmap(bmp, maxBitmapSize));
486                     }
487                 }
488             }
489         }
490 
491         /**
492          * Put a String value into the meta data. Custom keys may be used, but if
493          * the METADATA_KEYs defined in this class are used they may only be one
494          * of the following:
495          * <ul>
496          * <li>{@link #METADATA_KEY_RDS_PS}</li>
497          * <li>{@link #METADATA_KEY_RDS_RT}</li>
498          * <li>{@link #METADATA_KEY_TITLE}</li>
499          * <li>{@link #METADATA_KEY_ARTIST}</li>
500          * <li>{@link #METADATA_KEY_ALBUM}</li>
501          * <li>{@link #METADATA_KEY_GENRE}</li>
502          * </ul>
503          *
504          * @param key The key for referencing this value
505          * @param value The String value to store
506          * @return the same Builder instance
507          */
putString(String key, String value)508         public Builder putString(String key, String value) {
509             if (!METADATA_KEYS_TYPE.containsKey(key) ||
510                     METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
511                 throw new IllegalArgumentException("The " + key
512                         + " key cannot be used to put a String");
513             }
514             mBundle.putString(key, value);
515             return this;
516         }
517 
518         /**
519          * Put an int value into the meta data. Custom keys may be used, but if
520          * the METADATA_KEYs defined in this class are used they may only be one
521          * of the following:
522          * <ul>
523          * <li>{@link #METADATA_KEY_RDS_PI}</li>
524          * <li>{@link #METADATA_KEY_RDS_PTY}</li>
525          * <li>{@link #METADATA_KEY_RBDS_PTY}</li>
526          * </ul>
527          * or any bitmap represented by its identifier.
528          *
529          * @param key The key for referencing this value
530          * @param value The int value to store
531          * @return the same Builder instance
532          */
putInt(String key, int value)533         public Builder putInt(String key, int value) {
534             RadioMetadata.putInt(mBundle, key, value);
535             return this;
536         }
537 
538         /**
539          * Put a {@link Bitmap} into the meta data. Custom keys may be used, but
540          * if the METADATA_KEYs defined in this class are used they may only be
541          * one of the following:
542          * <ul>
543          * <li>{@link #METADATA_KEY_ICON}</li>
544          * <li>{@link #METADATA_KEY_ART}</li>
545          * </ul>
546          * <p>
547          *
548          * @param key The key for referencing this value
549          * @param value The Bitmap to store
550          * @return the same Builder instance
551          */
putBitmap(String key, Bitmap value)552         public Builder putBitmap(String key, Bitmap value) {
553             if (!METADATA_KEYS_TYPE.containsKey(key) ||
554                     METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
555                 throw new IllegalArgumentException("The " + key
556                         + " key cannot be used to put a Bitmap");
557             }
558             mBundle.putParcelable(key, value);
559             return this;
560         }
561 
562         /**
563          * Put a {@link RadioMetadata.Clock} into the meta data. Custom keys may be used, but if the
564          * METADATA_KEYs defined in this class are used they may only be one of the following:
565          * <ul>
566          * <li>{@link #MEADATA_KEY_CLOCK}</li>
567          * </ul>
568          *
569          * @param utcSecondsSinceEpoch Number of seconds since epoch for UTC + 0 timezone.
570          * @param timezoneOffsetInMinutes Offset of timezone from UTC + 0 in minutes.
571          * @return the same Builder instance.
572          */
putClock(String key, long utcSecondsSinceEpoch, int timezoneOffsetMinutes)573         public Builder putClock(String key, long utcSecondsSinceEpoch, int timezoneOffsetMinutes) {
574             if (!METADATA_KEYS_TYPE.containsKey(key) ||
575                     METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_CLOCK) {
576                 throw new IllegalArgumentException("The " + key
577                     + " key cannot be used to put a RadioMetadata.Clock.");
578             }
579             mBundle.putParcelable(key, new Clock(utcSecondsSinceEpoch, timezoneOffsetMinutes));
580             return this;
581         }
582 
583         /**
584          * Creates a {@link RadioMetadata} instance with the specified fields.
585          *
586          * @return a new {@link RadioMetadata} object
587          */
build()588         public RadioMetadata build() {
589             return new RadioMetadata(mBundle);
590         }
591 
scaleBitmap(Bitmap bmp, int maxSize)592         private Bitmap scaleBitmap(Bitmap bmp, int maxSize) {
593             float maxSizeF = maxSize;
594             float widthScale = maxSizeF / bmp.getWidth();
595             float heightScale = maxSizeF / bmp.getHeight();
596             float scale = Math.min(widthScale, heightScale);
597             int height = (int) (bmp.getHeight() * scale);
598             int width = (int) (bmp.getWidth() * scale);
599             return Bitmap.createScaledBitmap(bmp, width, height, true);
600         }
601     }
602 
putIntFromNative(int nativeKey, int value)603     int putIntFromNative(int nativeKey, int value) {
604         String key = getKeyFromNativeKey(nativeKey);
605         try {
606             putInt(mBundle, key, value);
607             return 0;
608         } catch (IllegalArgumentException ex) {
609             return -1;
610         }
611     }
612 
putStringFromNative(int nativeKey, String value)613     int putStringFromNative(int nativeKey, String value) {
614         String key = getKeyFromNativeKey(nativeKey);
615         if (!METADATA_KEYS_TYPE.containsKey(key) ||
616                 METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
617             return -1;
618         }
619         mBundle.putString(key, value);
620         return 0;
621     }
622 
putBitmapFromNative(int nativeKey, byte[] value)623     int putBitmapFromNative(int nativeKey, byte[] value) {
624         String key = getKeyFromNativeKey(nativeKey);
625         if (!METADATA_KEYS_TYPE.containsKey(key) ||
626                 METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
627             return -1;
628         }
629         Bitmap bmp = null;
630         try {
631             bmp = BitmapFactory.decodeByteArray(value, 0, value.length);
632             if (bmp != null) {
633                 mBundle.putParcelable(key, bmp);
634                 return 0;
635             }
636         } catch (Exception e) {
637         }
638         return -1;
639     }
640 
putClockFromNative(int nativeKey, long utcEpochSeconds, int timezoneOffsetInMinutes)641     int putClockFromNative(int nativeKey, long utcEpochSeconds, int timezoneOffsetInMinutes) {
642         String key = getKeyFromNativeKey(nativeKey);
643         if (!METADATA_KEYS_TYPE.containsKey(key) ||
644                 METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_CLOCK) {
645               return -1;
646         }
647         mBundle.putParcelable(key, new RadioMetadata.Clock(
648             utcEpochSeconds, timezoneOffsetInMinutes));
649         return 0;
650     }
651 }
652