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.Nullable;
20 import android.annotation.SystemApi;
21 import android.graphics.Bitmap;
22 import android.graphics.BitmapFactory;
23 import android.os.Bundle;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.util.ArrayMap;
27 import android.util.Log;
28 import android.util.SparseArray;
29 
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.List;
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 = "BroadcastRadio.metadata";
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      * Technology-independent program name (station name).
105      */
106     public static final String METADATA_KEY_PROGRAM_NAME =
107             "android.hardware.radio.metadata.PROGRAM_NAME";
108 
109     /**
110      * DAB ensemble name.
111      */
112     public static final String METADATA_KEY_DAB_ENSEMBLE_NAME =
113             "android.hardware.radio.metadata.DAB_ENSEMBLE_NAME";
114 
115     /**
116      * DAB ensemble name - short version (up to 8 characters).
117      */
118     public static final String METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT =
119             "android.hardware.radio.metadata.DAB_ENSEMBLE_NAME_SHORT";
120 
121     /**
122      * DAB service name.
123      */
124     public static final String METADATA_KEY_DAB_SERVICE_NAME =
125             "android.hardware.radio.metadata.DAB_SERVICE_NAME";
126 
127     /**
128      * DAB service name - short version (up to 8 characters).
129      */
130     public static final String METADATA_KEY_DAB_SERVICE_NAME_SHORT =
131             "android.hardware.radio.metadata.DAB_SERVICE_NAME_SHORT";
132 
133     /**
134      * DAB component name.
135      */
136     public static final String METADATA_KEY_DAB_COMPONENT_NAME =
137             "android.hardware.radio.metadata.DAB_COMPONENT_NAME";
138 
139     /**
140      * DAB component name.
141      */
142     public static final String METADATA_KEY_DAB_COMPONENT_NAME_SHORT =
143             "android.hardware.radio.metadata.DAB_COMPONENT_NAME_SHORT";
144 
145 
146     private static final int METADATA_TYPE_INVALID = -1;
147     private static final int METADATA_TYPE_INT = 0;
148     private static final int METADATA_TYPE_TEXT = 1;
149     private static final int METADATA_TYPE_BITMAP = 2;
150     private static final int METADATA_TYPE_CLOCK = 3;
151 
152     private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
153 
154     static {
155         METADATA_KEYS_TYPE = new ArrayMap<String, Integer>();
METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PI, METADATA_TYPE_INT)156         METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PI, METADATA_TYPE_INT);
METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PS, METADATA_TYPE_TEXT)157         METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PS, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PTY, METADATA_TYPE_INT)158         METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PTY, METADATA_TYPE_INT);
METADATA_KEYS_TYPE.put(METADATA_KEY_RBDS_PTY, METADATA_TYPE_INT)159         METADATA_KEYS_TYPE.put(METADATA_KEY_RBDS_PTY, METADATA_TYPE_INT);
METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_RT, METADATA_TYPE_TEXT)160         METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_RT, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT)161         METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT)162         METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT)163         METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT)164         METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_ICON, METADATA_TYPE_BITMAP)165         METADATA_KEYS_TYPE.put(METADATA_KEY_ICON, METADATA_TYPE_BITMAP);
METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP)166         METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
METADATA_KEYS_TYPE.put(METADATA_KEY_CLOCK, METADATA_TYPE_CLOCK)167         METADATA_KEYS_TYPE.put(METADATA_KEY_CLOCK, METADATA_TYPE_CLOCK);
METADATA_KEYS_TYPE.put(METADATA_KEY_PROGRAM_NAME, METADATA_TYPE_TEXT)168         METADATA_KEYS_TYPE.put(METADATA_KEY_PROGRAM_NAME, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_ENSEMBLE_NAME, METADATA_TYPE_TEXT)169         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)170         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)171         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)172         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)173         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)174         METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_COMPONENT_NAME_SHORT, METADATA_TYPE_TEXT);
175     }
176 
177     // keep in sync with: system/media/radio/include/system/radio_metadata.h
178     private static final int NATIVE_KEY_INVALID     = -1;
179     private static final int NATIVE_KEY_RDS_PI      = 0;
180     private static final int NATIVE_KEY_RDS_PS      = 1;
181     private static final int NATIVE_KEY_RDS_PTY     = 2;
182     private static final int NATIVE_KEY_RBDS_PTY    = 3;
183     private static final int NATIVE_KEY_RDS_RT      = 4;
184     private static final int NATIVE_KEY_TITLE       = 5;
185     private static final int NATIVE_KEY_ARTIST      = 6;
186     private static final int NATIVE_KEY_ALBUM       = 7;
187     private static final int NATIVE_KEY_GENRE       = 8;
188     private static final int NATIVE_KEY_ICON        = 9;
189     private static final int NATIVE_KEY_ART         = 10;
190     private static final int NATIVE_KEY_CLOCK       = 11;
191 
192     private static final SparseArray<String> NATIVE_KEY_MAPPING;
193 
194     static {
195         NATIVE_KEY_MAPPING = new SparseArray<String>();
NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PI, METADATA_KEY_RDS_PI)196         NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PI, METADATA_KEY_RDS_PI);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PS, METADATA_KEY_RDS_PS)197         NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PS, METADATA_KEY_RDS_PS);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PTY, METADATA_KEY_RDS_PTY)198         NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PTY, METADATA_KEY_RDS_PTY);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_RBDS_PTY, METADATA_KEY_RBDS_PTY)199         NATIVE_KEY_MAPPING.put(NATIVE_KEY_RBDS_PTY, METADATA_KEY_RBDS_PTY);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_RT, METADATA_KEY_RDS_RT)200         NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_RT, METADATA_KEY_RDS_RT);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_TITLE, METADATA_KEY_TITLE)201         NATIVE_KEY_MAPPING.put(NATIVE_KEY_TITLE, METADATA_KEY_TITLE);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_ARTIST, METADATA_KEY_ARTIST)202         NATIVE_KEY_MAPPING.put(NATIVE_KEY_ARTIST, METADATA_KEY_ARTIST);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_ALBUM, METADATA_KEY_ALBUM)203         NATIVE_KEY_MAPPING.put(NATIVE_KEY_ALBUM, METADATA_KEY_ALBUM);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_GENRE, METADATA_KEY_GENRE)204         NATIVE_KEY_MAPPING.put(NATIVE_KEY_GENRE, METADATA_KEY_GENRE);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_ICON, METADATA_KEY_ICON)205         NATIVE_KEY_MAPPING.put(NATIVE_KEY_ICON, METADATA_KEY_ICON);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_ART, METADATA_KEY_ART)206         NATIVE_KEY_MAPPING.put(NATIVE_KEY_ART, METADATA_KEY_ART);
NATIVE_KEY_MAPPING.put(NATIVE_KEY_CLOCK, METADATA_KEY_CLOCK)207         NATIVE_KEY_MAPPING.put(NATIVE_KEY_CLOCK, METADATA_KEY_CLOCK);
208     }
209 
210     /**
211      * Provides a Clock that can be used to describe time as provided by the Radio.
212      *
213      * The clock is defined by the seconds since epoch at the UTC + 0 timezone
214      * and timezone offset from UTC + 0 represented in number of minutes.
215      *
216      * @hide
217      */
218     @SystemApi
219     public static final class Clock implements Parcelable {
220         private final long mUtcEpochSeconds;
221         private final int mTimezoneOffsetMinutes;
222 
describeContents()223         public int describeContents() {
224             return 0;
225         }
226 
writeToParcel(Parcel out, int flags)227         public void writeToParcel(Parcel out, int flags) {
228             out.writeLong(mUtcEpochSeconds);
229             out.writeInt(mTimezoneOffsetMinutes);
230         }
231 
232         public static final @android.annotation.NonNull Parcelable.Creator<Clock> CREATOR
233                 = new Parcelable.Creator<Clock>() {
234             public Clock createFromParcel(Parcel in) {
235                 return new Clock(in);
236             }
237 
238             public Clock[] newArray(int size) {
239                 return new Clock[size];
240             }
241         };
242 
Clock(long utcEpochSeconds, int timezoneOffsetMinutes)243         public Clock(long utcEpochSeconds, int timezoneOffsetMinutes) {
244             mUtcEpochSeconds = utcEpochSeconds;
245             mTimezoneOffsetMinutes = timezoneOffsetMinutes;
246         }
247 
Clock(Parcel in)248         private Clock(Parcel in) {
249             mUtcEpochSeconds = in.readLong();
250             mTimezoneOffsetMinutes = in.readInt();
251         }
252 
getUtcEpochSeconds()253         public long getUtcEpochSeconds() {
254             return mUtcEpochSeconds;
255         }
256 
getTimezoneOffsetMinutes()257         public int getTimezoneOffsetMinutes() {
258             return mTimezoneOffsetMinutes;
259         }
260     }
261 
262     private final Bundle mBundle;
263 
264     // Lazily computed hash code based upon the contents of mBundle.
265     private Integer mHashCode;
266 
267     @Override
hashCode()268     public int hashCode() {
269         if (mHashCode == null) {
270             List<String> keys = new ArrayList<String>(mBundle.keySet());
271             keys.sort(null);
272             Object[] objs = new Object[2 * keys.size()];
273             for (int i = 0; i < keys.size(); i++) {
274                 objs[2 * i] = keys.get(i);
275                 objs[2 * i + 1] = mBundle.get(keys.get(i));
276             }
277             mHashCode = Arrays.hashCode(objs);
278         }
279         return mHashCode;
280     }
281 
282     @Override
equals(@ullable Object obj)283     public boolean equals(@Nullable Object obj) {
284         if (this == obj) return true;
285         if (!(obj instanceof RadioMetadata)) return false;
286         Bundle otherBundle = ((RadioMetadata) obj).mBundle;
287         if (!mBundle.keySet().equals(otherBundle.keySet())) {
288             return false;
289         }
290         for (String key : mBundle.keySet()) {
291             // This logic will return a false negative if we ever put Bundles into mBundle. As of
292             // 2019-04-09, we only put ints, Strings, and Parcelables in, so it's fine for now.
293             if (!mBundle.get(key).equals(otherBundle.get(key))) {
294                 return false;
295             }
296         }
297         return true;
298     }
299 
RadioMetadata()300     RadioMetadata() {
301         mBundle = new Bundle();
302     }
303 
RadioMetadata(Bundle bundle)304     private RadioMetadata(Bundle bundle) {
305         mBundle = new Bundle(bundle);
306     }
307 
RadioMetadata(Parcel in)308     private RadioMetadata(Parcel in) {
309         mBundle = in.readBundle();
310     }
311 
312     @NonNull
313     @Override
toString()314     public String toString() {
315         StringBuilder sb = new StringBuilder("RadioMetadata[");
316 
317         final String removePrefix = "android.hardware.radio.metadata";
318 
319         boolean first = true;
320         for (String key : mBundle.keySet()) {
321             if (first) first = false;
322             else sb.append(", ");
323 
324             String keyDisp = key;
325             if (key.startsWith(removePrefix)) keyDisp = key.substring(removePrefix.length());
326 
327             sb.append(keyDisp);
328             sb.append('=');
329             sb.append(mBundle.get(key));
330         }
331 
332         sb.append("]");
333         return sb.toString();
334     }
335 
336     /**
337      * Returns {@code true} if the given key is contained in the meta data
338      *
339      * @param key a String key
340      * @return {@code true} if the key exists in this meta data, {@code false} otherwise
341      */
containsKey(String key)342     public boolean containsKey(String key) {
343         return mBundle.containsKey(key);
344     }
345 
346     /**
347      * Returns the text value associated with the given key as a String, or null
348      * if the key is not found in the meta data.
349      *
350      * @param key The key the value is stored under
351      * @return a String value, or null
352      */
getString(String key)353     public String getString(String key) {
354         return mBundle.getString(key);
355     }
356 
putInt(Bundle bundle, String key, int value)357     private static void putInt(Bundle bundle, String key, int value) {
358         int type = METADATA_KEYS_TYPE.getOrDefault(key, METADATA_TYPE_INVALID);
359         if (type != METADATA_TYPE_INT && type != METADATA_TYPE_BITMAP) {
360             throw new IllegalArgumentException("The " + key + " key cannot be used to put an int");
361         }
362         bundle.putInt(key, value);
363     }
364 
365     /**
366      * Returns the value associated with the given key,
367      * or 0 if the key is not found in the meta data.
368      *
369      * @param key The key the value is stored under
370      * @return an int value
371      */
getInt(String key)372     public int getInt(String key) {
373         return mBundle.getInt(key, 0);
374     }
375 
376     /**
377      * Returns a {@link Bitmap} for the given key or null if the key is not found in the meta data.
378      *
379      * @param key The key the value is stored under
380      * @return a {@link Bitmap} or null
381      * @deprecated Use getBitmapId(String) instead
382      */
383     @Deprecated
getBitmap(String key)384     public Bitmap getBitmap(String key) {
385         Bitmap bmp = null;
386         try {
387             bmp = mBundle.getParcelable(key);
388         } catch (Exception e) {
389             // ignore, value was not a bitmap
390             Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
391         }
392         return bmp;
393     }
394 
395     /**
396      * Retrieves an identifier for a bitmap.
397      *
398      * The format of an identifier is opaque to the application,
399      * with a special case of value 0 being invalid.
400      * An identifier for a given image-tuner pair is unique, so an application
401      * may cache images and determine if there is a necessity to fetch them
402      * again - if identifier changes, it means the image has changed.
403      *
404      * Only bitmap keys may be used with this method:
405      * <ul>
406      * <li>{@link #METADATA_KEY_ICON}</li>
407      * <li>{@link #METADATA_KEY_ART}</li>
408      * </ul>
409      *
410      * @param key The key the value is stored under.
411      * @return a bitmap identifier or 0 if it's missing.
412      * @hide This API is not thoroughly elaborated yet
413      */
getBitmapId(@onNull String key)414     public int getBitmapId(@NonNull String key) {
415         if (!METADATA_KEY_ICON.equals(key) && !METADATA_KEY_ART.equals(key)) return 0;
416         return getInt(key);
417     }
418 
getClock(String key)419     public Clock getClock(String key) {
420         Clock clock = null;
421         try {
422             clock = mBundle.getParcelable(key);
423         } catch (Exception e) {
424             // ignore, value was not a clock.
425             Log.w(TAG, "Failed to retrieve a key as Clock.", e);
426         }
427         return clock;
428     }
429 
430     @Override
describeContents()431     public int describeContents() {
432         return 0;
433     }
434 
435     @Override
writeToParcel(Parcel dest, int flags)436     public void writeToParcel(Parcel dest, int flags) {
437         dest.writeBundle(mBundle);
438     }
439 
440     /**
441      * Returns the number of fields in this meta data.
442      *
443      * @return the number of fields in the meta data.
444      */
size()445     public int size() {
446         return mBundle.size();
447     }
448 
449     /**
450      * Returns a Set containing the Strings used as keys in this meta data.
451      *
452      * @return a Set of String keys
453      */
keySet()454     public Set<String> keySet() {
455         return mBundle.keySet();
456     }
457 
458     /**
459      * Helper for getting the String key used by {@link RadioMetadata} from the
460      * corrsponding native integer key.
461      *
462      * @param editorKey The key used by the editor
463      * @return the key used by this class or null if no mapping exists
464      * @hide
465      */
getKeyFromNativeKey(int nativeKey)466     public static String getKeyFromNativeKey(int nativeKey) {
467         return NATIVE_KEY_MAPPING.get(nativeKey, null);
468     }
469 
470     public static final @android.annotation.NonNull Parcelable.Creator<RadioMetadata> CREATOR =
471             new Parcelable.Creator<RadioMetadata>() {
472                 @Override
473                 public RadioMetadata createFromParcel(Parcel in) {
474                     return new RadioMetadata(in);
475                 }
476 
477                 @Override
478                 public RadioMetadata[] newArray(int size) {
479                     return new RadioMetadata[size];
480                 }
481             };
482 
483     /**
484      * Use to build RadioMetadata objects.
485      */
486     public static final class Builder {
487         private final Bundle mBundle;
488 
489         /**
490          * Create an empty Builder. Any field that should be included in the
491          * {@link RadioMetadata} must be added.
492          */
Builder()493         public Builder() {
494             mBundle = new Bundle();
495         }
496 
497         /**
498          * Create a Builder using a {@link RadioMetadata} instance to set the
499          * initial values. All fields in the source meta data will be included in
500          * the new meta data. Fields can be overwritten by adding the same key.
501          *
502          * @param source
503          */
Builder(RadioMetadata source)504         public Builder(RadioMetadata source) {
505             mBundle = new Bundle(source.mBundle);
506         }
507 
508         /**
509          * Create a Builder using a {@link RadioMetadata} instance to set
510          * initial values, but replace bitmaps with a scaled down copy if they
511          * are larger than maxBitmapSize.
512          *
513          * @param source The original meta data to copy.
514          * @param maxBitmapSize The maximum height/width for bitmaps contained
515          *            in the meta data.
516          * @hide
517          */
Builder(RadioMetadata source, int maxBitmapSize)518         public Builder(RadioMetadata source, int maxBitmapSize) {
519             this(source);
520             for (String key : mBundle.keySet()) {
521                 Object value = mBundle.get(key);
522                 if (value != null && value instanceof Bitmap) {
523                     Bitmap bmp = (Bitmap) value;
524                     if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) {
525                         putBitmap(key, scaleBitmap(bmp, maxBitmapSize));
526                     }
527                 }
528             }
529         }
530 
531         /**
532          * Put a String value into the meta data. Custom keys may be used, but if
533          * the METADATA_KEYs defined in this class are used they may only be one
534          * of the following:
535          * <ul>
536          * <li>{@link #METADATA_KEY_RDS_PS}</li>
537          * <li>{@link #METADATA_KEY_RDS_RT}</li>
538          * <li>{@link #METADATA_KEY_TITLE}</li>
539          * <li>{@link #METADATA_KEY_ARTIST}</li>
540          * <li>{@link #METADATA_KEY_ALBUM}</li>
541          * <li>{@link #METADATA_KEY_GENRE}</li>
542          * </ul>
543          *
544          * @param key The key for referencing this value
545          * @param value The String value to store
546          * @return the same Builder instance
547          */
putString(String key, String value)548         public Builder putString(String key, String value) {
549             if (!METADATA_KEYS_TYPE.containsKey(key) ||
550                     METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
551                 throw new IllegalArgumentException("The " + key
552                         + " key cannot be used to put a String");
553             }
554             mBundle.putString(key, value);
555             return this;
556         }
557 
558         /**
559          * Put an int value into the meta data. Custom keys may be used, but if
560          * the METADATA_KEYs defined in this class are used they may only be one
561          * of the following:
562          * <ul>
563          * <li>{@link #METADATA_KEY_RDS_PI}</li>
564          * <li>{@link #METADATA_KEY_RDS_PTY}</li>
565          * <li>{@link #METADATA_KEY_RBDS_PTY}</li>
566          * </ul>
567          * or any bitmap represented by its identifier.
568          *
569          * @param key The key for referencing this value
570          * @param value The int value to store
571          * @return the same Builder instance
572          */
putInt(String key, int value)573         public Builder putInt(String key, int value) {
574             RadioMetadata.putInt(mBundle, key, value);
575             return this;
576         }
577 
578         /**
579          * Put a {@link Bitmap} into the meta data. Custom keys may be used, but
580          * if the METADATA_KEYs defined in this class are used they may only be
581          * one of the following:
582          * <ul>
583          * <li>{@link #METADATA_KEY_ICON}</li>
584          * <li>{@link #METADATA_KEY_ART}</li>
585          * </ul>
586          * <p>
587          *
588          * @param key The key for referencing this value
589          * @param value The Bitmap to store
590          * @return the same Builder instance
591          */
putBitmap(String key, Bitmap value)592         public Builder putBitmap(String key, Bitmap value) {
593             if (!METADATA_KEYS_TYPE.containsKey(key) ||
594                     METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
595                 throw new IllegalArgumentException("The " + key
596                         + " key cannot be used to put a Bitmap");
597             }
598             mBundle.putParcelable(key, value);
599             return this;
600         }
601 
602         /**
603          * Put a {@link RadioMetadata.Clock} into the meta data. Custom keys may be used, but if the
604          * METADATA_KEYs defined in this class are used they may only be one of the following:
605          * <ul>
606          * <li>{@link #MEADATA_KEY_CLOCK}</li>
607          * </ul>
608          *
609          * @param utcSecondsSinceEpoch Number of seconds since epoch for UTC + 0 timezone.
610          * @param timezoneOffsetInMinutes Offset of timezone from UTC + 0 in minutes.
611          * @return the same Builder instance.
612          */
putClock(String key, long utcSecondsSinceEpoch, int timezoneOffsetMinutes)613         public Builder putClock(String key, long utcSecondsSinceEpoch, int timezoneOffsetMinutes) {
614             if (!METADATA_KEYS_TYPE.containsKey(key) ||
615                     METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_CLOCK) {
616                 throw new IllegalArgumentException("The " + key
617                     + " key cannot be used to put a RadioMetadata.Clock.");
618             }
619             mBundle.putParcelable(key, new Clock(utcSecondsSinceEpoch, timezoneOffsetMinutes));
620             return this;
621         }
622 
623         /**
624          * Creates a {@link RadioMetadata} instance with the specified fields.
625          *
626          * @return a new {@link RadioMetadata} object
627          */
build()628         public RadioMetadata build() {
629             return new RadioMetadata(mBundle);
630         }
631 
scaleBitmap(Bitmap bmp, int maxSize)632         private Bitmap scaleBitmap(Bitmap bmp, int maxSize) {
633             float maxSizeF = maxSize;
634             float widthScale = maxSizeF / bmp.getWidth();
635             float heightScale = maxSizeF / bmp.getHeight();
636             float scale = Math.min(widthScale, heightScale);
637             int height = (int) (bmp.getHeight() * scale);
638             int width = (int) (bmp.getWidth() * scale);
639             return Bitmap.createScaledBitmap(bmp, width, height, true);
640         }
641     }
642 
putIntFromNative(int nativeKey, int value)643     int putIntFromNative(int nativeKey, int value) {
644         String key = getKeyFromNativeKey(nativeKey);
645         try {
646             putInt(mBundle, key, value);
647             // Invalidate mHashCode to force it to be recomputed.
648             mHashCode = null;
649             return 0;
650         } catch (IllegalArgumentException ex) {
651             return -1;
652         }
653     }
654 
putStringFromNative(int nativeKey, String value)655     int putStringFromNative(int nativeKey, String value) {
656         String key = getKeyFromNativeKey(nativeKey);
657         if (!METADATA_KEYS_TYPE.containsKey(key) ||
658                 METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
659             return -1;
660         }
661         mBundle.putString(key, value);
662         // Invalidate mHashCode to force it to be recomputed.
663         mHashCode = null;
664         return 0;
665     }
666 
putBitmapFromNative(int nativeKey, byte[] value)667     int putBitmapFromNative(int nativeKey, byte[] value) {
668         String key = getKeyFromNativeKey(nativeKey);
669         if (!METADATA_KEYS_TYPE.containsKey(key) ||
670                 METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
671             return -1;
672         }
673         Bitmap bmp = null;
674         try {
675             bmp = BitmapFactory.decodeByteArray(value, 0, value.length);
676             if (bmp != null) {
677                 mBundle.putParcelable(key, bmp);
678                 // Invalidate mHashCode to force it to be recomputed.
679                 mHashCode = null;
680                 return 0;
681             }
682         } catch (Exception e) {
683         }
684         return -1;
685     }
686 
putClockFromNative(int nativeKey, long utcEpochSeconds, int timezoneOffsetInMinutes)687     int putClockFromNative(int nativeKey, long utcEpochSeconds, int timezoneOffsetInMinutes) {
688         String key = getKeyFromNativeKey(nativeKey);
689         if (!METADATA_KEYS_TYPE.containsKey(key) ||
690                 METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_CLOCK) {
691               return -1;
692         }
693         mBundle.putParcelable(key, new RadioMetadata.Clock(
694             utcEpochSeconds, timezoneOffsetInMinutes));
695         // Invalidate mHashCode to force it to be recomputed.
696         mHashCode = null;
697         return 0;
698     }
699 }
700