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