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.FlaggedApi; 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SystemApi; 22 import android.graphics.Bitmap; 23 import android.graphics.BitmapFactory; 24 import android.os.Bundle; 25 import android.os.Parcel; 26 import android.os.Parcelable; 27 import android.util.ArrayMap; 28 import android.util.Log; 29 import android.util.SparseArray; 30 31 import java.util.ArrayList; 32 import java.util.Arrays; 33 import java.util.List; 34 import java.util.Objects; 35 import java.util.Set; 36 37 /** 38 * Contains meta data about a radio program such as station name, song title, artist etc... 39 * @hide 40 */ 41 @SystemApi 42 public final class RadioMetadata implements Parcelable { 43 private static final String TAG = "BroadcastRadio.metadata"; 44 45 /** 46 * The RDS Program Information. 47 */ 48 public static final String METADATA_KEY_RDS_PI = "android.hardware.radio.metadata.RDS_PI"; 49 50 /** 51 * The RDS Program Service. 52 */ 53 public static final String METADATA_KEY_RDS_PS = "android.hardware.radio.metadata.RDS_PS"; 54 55 /** 56 * The RDS PTY. 57 */ 58 public static final String METADATA_KEY_RDS_PTY = "android.hardware.radio.metadata.RDS_PTY"; 59 60 /** 61 * The RBDS PTY. 62 */ 63 public static final String METADATA_KEY_RBDS_PTY = "android.hardware.radio.metadata.RBDS_PTY"; 64 65 /** 66 * The RBDS Radio Text. 67 */ 68 public static final String METADATA_KEY_RDS_RT = "android.hardware.radio.metadata.RDS_RT"; 69 70 /** 71 * The song title. 72 */ 73 public static final String METADATA_KEY_TITLE = "android.hardware.radio.metadata.TITLE"; 74 75 /** 76 * The artist name. 77 */ 78 public static final String METADATA_KEY_ARTIST = "android.hardware.radio.metadata.ARTIST"; 79 80 /** 81 * The album name. 82 */ 83 public static final String METADATA_KEY_ALBUM = "android.hardware.radio.metadata.ALBUM"; 84 85 /** 86 * The music genre. 87 */ 88 public static final String METADATA_KEY_GENRE = "android.hardware.radio.metadata.GENRE"; 89 90 /** 91 * The radio station icon {@link Bitmap}. 92 */ 93 public static final String METADATA_KEY_ICON = "android.hardware.radio.metadata.ICON"; 94 95 /** 96 * The artwork for the song/album {@link Bitmap}. 97 */ 98 public static final String METADATA_KEY_ART = "android.hardware.radio.metadata.ART"; 99 100 /** 101 * The clock. 102 */ 103 public static final String METADATA_KEY_CLOCK = "android.hardware.radio.metadata.CLOCK"; 104 105 /** 106 * Technology-independent program name (station name). 107 */ 108 public static final String METADATA_KEY_PROGRAM_NAME = 109 "android.hardware.radio.metadata.PROGRAM_NAME"; 110 111 /** 112 * DAB ensemble name. 113 */ 114 public static final String METADATA_KEY_DAB_ENSEMBLE_NAME = 115 "android.hardware.radio.metadata.DAB_ENSEMBLE_NAME"; 116 117 /** 118 * DAB ensemble name - short version (up to 8 characters). 119 */ 120 public static final String METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT = 121 "android.hardware.radio.metadata.DAB_ENSEMBLE_NAME_SHORT"; 122 123 /** 124 * DAB service name. 125 */ 126 public static final String METADATA_KEY_DAB_SERVICE_NAME = 127 "android.hardware.radio.metadata.DAB_SERVICE_NAME"; 128 129 /** 130 * DAB service name - short version (up to 8 characters). 131 */ 132 public static final String METADATA_KEY_DAB_SERVICE_NAME_SHORT = 133 "android.hardware.radio.metadata.DAB_SERVICE_NAME_SHORT"; 134 135 /** 136 * DAB component name. 137 */ 138 public static final String METADATA_KEY_DAB_COMPONENT_NAME = 139 "android.hardware.radio.metadata.DAB_COMPONENT_NAME"; 140 141 /** 142 * DAB component name. 143 */ 144 public static final String METADATA_KEY_DAB_COMPONENT_NAME_SHORT = 145 "android.hardware.radio.metadata.DAB_COMPONENT_NAME_SHORT"; 146 147 /** 148 * Short context description of comment 149 * 150 * <p>Comment could relate to the current audio program content, or it might 151 * be unrelated information that the station chooses to send. It is composed 152 * of short content description and actual text (see NRSC-G200-A and id3v2.3.0 153 * for more info). 154 */ 155 @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) 156 public static final String METADATA_KEY_COMMENT_SHORT_DESCRIPTION = 157 "android.hardware.radio.metadata.COMMENT_SHORT_DESCRIPTION"; 158 159 /** 160 * Actual text of comment 161 * 162 * @see #METADATA_KEY_COMMENT_SHORT_DESCRIPTION 163 */ 164 @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) 165 public static final String METADATA_KEY_COMMENT_ACTUAL_TEXT = 166 "android.hardware.radio.metadata.COMMENT_ACTUAL_TEXT"; 167 168 /** 169 * Commercial 170 * 171 * <p>Commercial is application specific and generally used to facilitate the 172 * sale of products and services (see NRSC-G200-A and id3v2.3.0 for more info). 173 */ 174 @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) 175 public static final String METADATA_KEY_COMMERCIAL = 176 "android.hardware.radio.metadata.COMMERCIAL"; 177 178 /** 179 * Array of Unique File Identifiers 180 * 181 * <p>Unique File Identifier (UFID) can be used to transmit an alphanumeric 182 * identifier of the current content, or of an advertised product or 183 * service (see NRSC-G200-A and id3v2.3.0 for more info). 184 */ 185 @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) 186 public static final String METADATA_KEY_UFIDS = "android.hardware.radio.metadata.UFIDS"; 187 188 /** 189 * HD short station name or HD universal short station name 190 * 191 * <p>It can be up to 12 characters (see SY_IDD_1020s for more info). 192 */ 193 @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) 194 public static final String METADATA_KEY_HD_STATION_NAME_SHORT = 195 "android.hardware.radio.metadata.HD_STATION_NAME_SHORT"; 196 197 /** 198 * HD long station name, HD station slogan or HD station message 199 * 200 * <p>(see SY_IDD_1020s for more info) 201 */ 202 @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) 203 public static final String METADATA_KEY_HD_STATION_NAME_LONG = 204 "android.hardware.radio.metadata.HD_STATION_NAME_LONG"; 205 206 /** 207 * Bit mask of all HD Radio subchannels available 208 * 209 * <p>Bit {@link ProgramSelector#SUB_CHANNEL_HD_1} from LSB represents the 210 * availability of HD-1 subchannel (main program service, MPS). Bits 211 * {@link ProgramSelector#SUB_CHANNEL_HD_2} to {@link ProgramSelector#SUB_CHANNEL_HD_8} 212 * from LSB represent HD-2 to HD-8 subchannel (supplemental program services, SPS) 213 * respectively. 214 */ 215 @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) 216 public static final String METADATA_KEY_HD_SUBCHANNELS_AVAILABLE = 217 "android.hardware.radio.metadata.HD_SUBCHANNELS_AVAILABLE"; 218 219 private static final int METADATA_TYPE_INVALID = -1; 220 private static final int METADATA_TYPE_INT = 0; 221 private static final int METADATA_TYPE_TEXT = 1; 222 private static final int METADATA_TYPE_BITMAP = 2; 223 private static final int METADATA_TYPE_CLOCK = 3; 224 private static final int METADATA_TYPE_TEXT_ARRAY = 4; 225 226 private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE; 227 228 static { 229 METADATA_KEYS_TYPE = new ArrayMap<String, Integer>(); METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PI, METADATA_TYPE_INT)230 METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PI, METADATA_TYPE_INT); METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PS, METADATA_TYPE_TEXT)231 METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PS, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PTY, METADATA_TYPE_INT)232 METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PTY, METADATA_TYPE_INT); METADATA_KEYS_TYPE.put(METADATA_KEY_RBDS_PTY, METADATA_TYPE_INT)233 METADATA_KEYS_TYPE.put(METADATA_KEY_RBDS_PTY, METADATA_TYPE_INT); METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_RT, METADATA_TYPE_TEXT)234 METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_RT, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT)235 METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT)236 METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT)237 METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT)238 METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_ICON, METADATA_TYPE_BITMAP)239 METADATA_KEYS_TYPE.put(METADATA_KEY_ICON, METADATA_TYPE_BITMAP); METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP)240 METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP); METADATA_KEYS_TYPE.put(METADATA_KEY_CLOCK, METADATA_TYPE_CLOCK)241 METADATA_KEYS_TYPE.put(METADATA_KEY_CLOCK, METADATA_TYPE_CLOCK); METADATA_KEYS_TYPE.put(METADATA_KEY_PROGRAM_NAME, METADATA_TYPE_TEXT)242 METADATA_KEYS_TYPE.put(METADATA_KEY_PROGRAM_NAME, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_ENSEMBLE_NAME, METADATA_TYPE_TEXT)243 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)244 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)245 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)246 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)247 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)248 METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_COMPONENT_NAME_SHORT, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_COMMENT_SHORT_DESCRIPTION, METADATA_TYPE_TEXT)249 METADATA_KEYS_TYPE.put(METADATA_KEY_COMMENT_SHORT_DESCRIPTION, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_COMMENT_ACTUAL_TEXT, METADATA_TYPE_TEXT)250 METADATA_KEYS_TYPE.put(METADATA_KEY_COMMENT_ACTUAL_TEXT, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_COMMERCIAL, METADATA_TYPE_TEXT)251 METADATA_KEYS_TYPE.put(METADATA_KEY_COMMERCIAL, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_UFIDS, METADATA_TYPE_TEXT_ARRAY)252 METADATA_KEYS_TYPE.put(METADATA_KEY_UFIDS, METADATA_TYPE_TEXT_ARRAY); METADATA_KEYS_TYPE.put(METADATA_KEY_HD_STATION_NAME_SHORT, METADATA_TYPE_TEXT)253 METADATA_KEYS_TYPE.put(METADATA_KEY_HD_STATION_NAME_SHORT, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_HD_STATION_NAME_LONG, METADATA_TYPE_TEXT)254 METADATA_KEYS_TYPE.put(METADATA_KEY_HD_STATION_NAME_LONG, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_HD_SUBCHANNELS_AVAILABLE, METADATA_TYPE_INT)255 METADATA_KEYS_TYPE.put(METADATA_KEY_HD_SUBCHANNELS_AVAILABLE, METADATA_TYPE_INT); 256 } 257 258 // keep in sync with: system/media/radio/include/system/radio_metadata.h 259 private static final int NATIVE_KEY_INVALID = -1; 260 private static final int NATIVE_KEY_RDS_PI = 0; 261 private static final int NATIVE_KEY_RDS_PS = 1; 262 private static final int NATIVE_KEY_RDS_PTY = 2; 263 private static final int NATIVE_KEY_RBDS_PTY = 3; 264 private static final int NATIVE_KEY_RDS_RT = 4; 265 private static final int NATIVE_KEY_TITLE = 5; 266 private static final int NATIVE_KEY_ARTIST = 6; 267 private static final int NATIVE_KEY_ALBUM = 7; 268 private static final int NATIVE_KEY_GENRE = 8; 269 private static final int NATIVE_KEY_ICON = 9; 270 private static final int NATIVE_KEY_ART = 10; 271 private static final int NATIVE_KEY_CLOCK = 11; 272 273 private static final SparseArray<String> NATIVE_KEY_MAPPING; 274 275 static { 276 NATIVE_KEY_MAPPING = new SparseArray<String>(); NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PI, METADATA_KEY_RDS_PI)277 NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PI, METADATA_KEY_RDS_PI); NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PS, METADATA_KEY_RDS_PS)278 NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PS, METADATA_KEY_RDS_PS); NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PTY, METADATA_KEY_RDS_PTY)279 NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PTY, METADATA_KEY_RDS_PTY); NATIVE_KEY_MAPPING.put(NATIVE_KEY_RBDS_PTY, METADATA_KEY_RBDS_PTY)280 NATIVE_KEY_MAPPING.put(NATIVE_KEY_RBDS_PTY, METADATA_KEY_RBDS_PTY); NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_RT, METADATA_KEY_RDS_RT)281 NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_RT, METADATA_KEY_RDS_RT); NATIVE_KEY_MAPPING.put(NATIVE_KEY_TITLE, METADATA_KEY_TITLE)282 NATIVE_KEY_MAPPING.put(NATIVE_KEY_TITLE, METADATA_KEY_TITLE); NATIVE_KEY_MAPPING.put(NATIVE_KEY_ARTIST, METADATA_KEY_ARTIST)283 NATIVE_KEY_MAPPING.put(NATIVE_KEY_ARTIST, METADATA_KEY_ARTIST); NATIVE_KEY_MAPPING.put(NATIVE_KEY_ALBUM, METADATA_KEY_ALBUM)284 NATIVE_KEY_MAPPING.put(NATIVE_KEY_ALBUM, METADATA_KEY_ALBUM); NATIVE_KEY_MAPPING.put(NATIVE_KEY_GENRE, METADATA_KEY_GENRE)285 NATIVE_KEY_MAPPING.put(NATIVE_KEY_GENRE, METADATA_KEY_GENRE); NATIVE_KEY_MAPPING.put(NATIVE_KEY_ICON, METADATA_KEY_ICON)286 NATIVE_KEY_MAPPING.put(NATIVE_KEY_ICON, METADATA_KEY_ICON); NATIVE_KEY_MAPPING.put(NATIVE_KEY_ART, METADATA_KEY_ART)287 NATIVE_KEY_MAPPING.put(NATIVE_KEY_ART, METADATA_KEY_ART); NATIVE_KEY_MAPPING.put(NATIVE_KEY_CLOCK, METADATA_KEY_CLOCK)288 NATIVE_KEY_MAPPING.put(NATIVE_KEY_CLOCK, METADATA_KEY_CLOCK); 289 } 290 291 /** 292 * Provides a Clock that can be used to describe time as provided by the Radio. 293 * 294 * <p>The clock time is defined by the seconds since epoch at the UTC + 0 timezone 295 * and timezone offset from UTC + 0 represented in number of minutes. 296 * 297 * @hide 298 */ 299 @SystemApi 300 public static final class Clock implements Parcelable { 301 private final long mUtcEpochSeconds; 302 private final int mTimezoneOffsetMinutes; 303 describeContents()304 public int describeContents() { 305 return 0; 306 } 307 writeToParcel(Parcel out, int flags)308 public void writeToParcel(Parcel out, int flags) { 309 out.writeLong(mUtcEpochSeconds); 310 out.writeInt(mTimezoneOffsetMinutes); 311 } 312 313 public static final @android.annotation.NonNull Parcelable.Creator<Clock> CREATOR 314 = new Parcelable.Creator<Clock>() { 315 public Clock createFromParcel(Parcel in) { 316 return new Clock(in); 317 } 318 319 public Clock[] newArray(int size) { 320 return new Clock[size]; 321 } 322 }; 323 Clock(long utcEpochSeconds, int timezoneOffsetMinutes)324 public Clock(long utcEpochSeconds, int timezoneOffsetMinutes) { 325 mUtcEpochSeconds = utcEpochSeconds; 326 mTimezoneOffsetMinutes = timezoneOffsetMinutes; 327 } 328 Clock(Parcel in)329 private Clock(Parcel in) { 330 mUtcEpochSeconds = in.readLong(); 331 mTimezoneOffsetMinutes = in.readInt(); 332 } 333 getUtcEpochSeconds()334 public long getUtcEpochSeconds() { 335 return mUtcEpochSeconds; 336 } 337 getTimezoneOffsetMinutes()338 public int getTimezoneOffsetMinutes() { 339 return mTimezoneOffsetMinutes; 340 } 341 } 342 343 private final Bundle mBundle; 344 345 // Lazily computed hash code based upon the contents of mBundle. 346 private Integer mHashCode; 347 348 @Override hashCode()349 public int hashCode() { 350 if (mHashCode == null) { 351 List<String> keys = new ArrayList<String>(mBundle.keySet()); 352 keys.sort(null); 353 Object[] objs = new Object[2 * keys.size()]; 354 for (int i = 0; i < keys.size(); i++) { 355 objs[2 * i] = keys.get(i); 356 objs[2 * i + 1] = mBundle.get(keys.get(i)); 357 } 358 mHashCode = Arrays.hashCode(objs); 359 } 360 return mHashCode; 361 } 362 363 @Override equals(@ullable Object obj)364 public boolean equals(@Nullable Object obj) { 365 if (this == obj) return true; 366 if (!(obj instanceof RadioMetadata)) return false; 367 Bundle otherBundle = ((RadioMetadata) obj).mBundle; 368 if (!mBundle.keySet().equals(otherBundle.keySet())) { 369 return false; 370 } 371 for (String key : mBundle.keySet()) { 372 if (Flags.hdRadioImproved() && Objects.equals(METADATA_KEYS_TYPE.get(key), 373 METADATA_TYPE_TEXT_ARRAY)) { 374 if (!Arrays.equals(mBundle.getStringArray(key), otherBundle.getStringArray(key))) { 375 return false; 376 } 377 } else if (!Objects.equals(mBundle.get(key), otherBundle.get(key))) { 378 return false; 379 } 380 } 381 return true; 382 } 383 RadioMetadata()384 RadioMetadata() { 385 mBundle = new Bundle(); 386 } 387 RadioMetadata(Bundle bundle)388 private RadioMetadata(Bundle bundle) { 389 mBundle = new Bundle(bundle); 390 } 391 RadioMetadata(Parcel in)392 private RadioMetadata(Parcel in) { 393 mBundle = in.readBundle(); 394 } 395 396 @NonNull 397 @Override toString()398 public String toString() { 399 StringBuilder sb = new StringBuilder("RadioMetadata["); 400 401 final String removePrefix = "android.hardware.radio.metadata"; 402 403 boolean first = true; 404 for (String key : mBundle.keySet()) { 405 if (first) first = false; 406 else sb.append(", "); 407 408 String keyDisp = key; 409 if (key.startsWith(removePrefix)) keyDisp = key.substring(removePrefix.length()); 410 411 sb.append(keyDisp); 412 sb.append('='); 413 if (Flags.hdRadioImproved() && Objects.equals(METADATA_KEYS_TYPE.get(key), 414 METADATA_TYPE_TEXT_ARRAY)) { 415 String[] stringArrayValue = mBundle.getStringArray(key); 416 sb.append('['); 417 for (int i = 0; i < stringArrayValue.length; i++) { 418 if (i != 0) { 419 sb.append(','); 420 } 421 sb.append(stringArrayValue[i]); 422 } 423 sb.append(']'); 424 } else { 425 sb.append(mBundle.get(key)); 426 } 427 428 } 429 430 sb.append("]"); 431 return sb.toString(); 432 } 433 434 /** 435 * Returns {@code true} if the given key is contained in the meta data 436 * 437 * @param key a String key 438 * @return {@code true} if the key exists in this meta data, {@code false} otherwise 439 */ containsKey(String key)440 public boolean containsKey(String key) { 441 return mBundle.containsKey(key); 442 } 443 444 /** 445 * Returns the text value associated with the given key as a String, or null 446 * if the key is not found in the meta data. 447 * 448 * @param key The key the value is stored under 449 * @return a String value, or null 450 */ getString(String key)451 public String getString(String key) { 452 return mBundle.getString(key); 453 } 454 putInt(Bundle bundle, String key, int value)455 private static void putInt(Bundle bundle, String key, int value) { 456 int type = METADATA_KEYS_TYPE.getOrDefault(key, METADATA_TYPE_INVALID); 457 if (type != METADATA_TYPE_INT && type != METADATA_TYPE_BITMAP) { 458 throw new IllegalArgumentException("The " + key + " key cannot be used to put an int"); 459 } 460 bundle.putInt(key, value); 461 } 462 463 /** 464 * Returns the value associated with the given key, 465 * or 0 if the key is not found in the meta data. 466 * 467 * @param key The key the value is stored under 468 * @return an int value 469 */ getInt(String key)470 public int getInt(String key) { 471 return mBundle.getInt(key, 0); 472 } 473 474 /** 475 * Returns a {@link Bitmap} for the given key or null if the key is not found in the meta data. 476 * 477 * @param key The key the value is stored under 478 * @return a {@link Bitmap} or null 479 * @deprecated Use getBitmapId(String) instead 480 */ 481 @Deprecated getBitmap(String key)482 public Bitmap getBitmap(String key) { 483 Bitmap bmp = null; 484 try { 485 bmp = mBundle.getParcelable(key, android.graphics.Bitmap.class); 486 } catch (Exception e) { 487 // ignore, value was not a bitmap 488 Log.w(TAG, "Failed to retrieve a key as Bitmap.", e); 489 } 490 return bmp; 491 } 492 493 /** 494 * Retrieves an identifier for a bitmap. 495 * 496 * <p>The format of an identifier is opaque to the application, 497 * with a special case of value 0 being invalid. 498 * An identifier for a given image-tuner pair is unique, so an application 499 * may cache images and determine if there is a necessity to fetch them 500 * again - if identifier changes, it means the image has changed. 501 * 502 * <p>Only bitmap keys may be used with this method: 503 * <ul> 504 * <li>{@link #METADATA_KEY_ICON}</li> 505 * <li>{@link #METADATA_KEY_ART}</li> 506 * </ul> 507 * 508 * @param key The key the value is stored under. 509 * @return a bitmap identifier or 0 if it's missing. 510 * @throws NullPointerException if metadata key is {@code null} 511 * @throws IllegalArgumentException if the metadata with the key is not found in 512 * metadata or the key is not of bitmap-key type 513 */ 514 @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) getBitmapId(@onNull String key)515 public int getBitmapId(@NonNull String key) { 516 Objects.requireNonNull(key, "Metadata key can not be null"); 517 if (!METADATA_KEY_ICON.equals(key) && !METADATA_KEY_ART.equals(key)) { 518 throw new IllegalArgumentException("Failed to retrieve key " + key + " as bitmap key"); 519 } 520 return getInt(key); 521 } 522 getClock(String key)523 public Clock getClock(String key) { 524 Clock clock = null; 525 try { 526 clock = mBundle.getParcelable(key, android.hardware.radio.RadioMetadata.Clock.class); 527 } catch (Exception e) { 528 // ignore, value was not a clock. 529 Log.w(TAG, "Failed to retrieve a key as Clock.", e); 530 } 531 return clock; 532 } 533 534 /** 535 * Gets the string array value associated with the given key as a string 536 * array. 537 * 538 * <p>Only string array keys may be used with this method: 539 * <ul> 540 * <li>{@link #METADATA_KEY_UFIDS}</li> 541 * </ul> 542 * 543 * @param key The key the value is stored under 544 * @return String array of the given string-array-type key 545 * @throws NullPointerException if metadata key is {@code null} 546 * @throws IllegalArgumentException if the metadata with the key is not found in 547 * metadata or the key is not of string-array type 548 */ 549 @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) 550 @NonNull getStringArray(@onNull String key)551 public String[] getStringArray(@NonNull String key) { 552 Objects.requireNonNull(key, "Metadata key can not be null"); 553 if (!Objects.equals(METADATA_KEYS_TYPE.get(key), METADATA_TYPE_TEXT_ARRAY)) { 554 throw new IllegalArgumentException("Failed to retrieve key " + key 555 + " as string array"); 556 } 557 String[] stringArrayValue = mBundle.getStringArray(key); 558 if (stringArrayValue == null) { 559 throw new IllegalArgumentException("Key " + key + " is not found in metadata"); 560 } 561 return stringArrayValue; 562 } 563 564 @Override describeContents()565 public int describeContents() { 566 return 0; 567 } 568 569 @Override writeToParcel(Parcel dest, int flags)570 public void writeToParcel(Parcel dest, int flags) { 571 dest.writeBundle(mBundle); 572 } 573 574 /** 575 * Returns the number of fields in this meta data. 576 * 577 * @return the number of fields in the meta data. 578 */ size()579 public int size() { 580 return mBundle.size(); 581 } 582 583 /** 584 * Returns a Set containing the Strings used as keys in this meta data. 585 * 586 * @return a Set of String keys 587 */ keySet()588 public Set<String> keySet() { 589 return mBundle.keySet(); 590 } 591 592 /** 593 * Helper for getting the String key used by {@link RadioMetadata} from the 594 * corrsponding native integer key. 595 * 596 * @param nativeKey The key used by the editor 597 * @return the key used by this class or null if no mapping exists 598 * @hide 599 */ getKeyFromNativeKey(int nativeKey)600 public static String getKeyFromNativeKey(int nativeKey) { 601 return NATIVE_KEY_MAPPING.get(nativeKey, null); 602 } 603 604 public static final @android.annotation.NonNull Parcelable.Creator<RadioMetadata> CREATOR = 605 new Parcelable.Creator<RadioMetadata>() { 606 @Override 607 public RadioMetadata createFromParcel(Parcel in) { 608 return new RadioMetadata(in); 609 } 610 611 @Override 612 public RadioMetadata[] newArray(int size) { 613 return new RadioMetadata[size]; 614 } 615 }; 616 617 /** 618 * Use to build RadioMetadata objects. 619 */ 620 public static final class Builder { 621 private final Bundle mBundle; 622 623 /** 624 * Create an empty Builder. Any field that should be included in the 625 * {@link RadioMetadata} must be added. 626 */ Builder()627 public Builder() { 628 mBundle = new Bundle(); 629 } 630 631 /** 632 * Create a Builder using a {@link RadioMetadata} instance to set the 633 * initial values. All fields in the source meta data will be included in 634 * the new meta data. Fields can be overwritten by adding the same key. 635 * 636 * @param source 637 */ Builder(RadioMetadata source)638 public Builder(RadioMetadata source) { 639 mBundle = new Bundle(source.mBundle); 640 } 641 642 /** 643 * Create a Builder using a {@link RadioMetadata} instance to set 644 * initial values, but replace bitmaps with a scaled down copy if they 645 * are larger than maxBitmapSize. 646 * 647 * @param source The original meta data to copy. 648 * @param maxBitmapSize The maximum height/width for bitmaps contained 649 * in the meta data. 650 * @hide 651 */ Builder(RadioMetadata source, int maxBitmapSize)652 public Builder(RadioMetadata source, int maxBitmapSize) { 653 this(source); 654 for (String key : mBundle.keySet()) { 655 Object value = mBundle.get(key); 656 if (value != null && value instanceof Bitmap) { 657 Bitmap bmp = (Bitmap) value; 658 if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) { 659 putBitmap(key, scaleBitmap(bmp, maxBitmapSize)); 660 } 661 } 662 } 663 } 664 665 /** 666 * Put a String value into the meta data. Custom keys may be used, but if 667 * the METADATA_KEYs defined in this class are used they may only be one 668 * of the following: 669 * <ul> 670 * <li>{@link #METADATA_KEY_RDS_PS}</li> 671 * <li>{@link #METADATA_KEY_RDS_RT}</li> 672 * <li>{@link #METADATA_KEY_TITLE}</li> 673 * <li>{@link #METADATA_KEY_ARTIST}</li> 674 * <li>{@link #METADATA_KEY_ALBUM}</li> 675 * <li>{@link #METADATA_KEY_GENRE}</li> 676 * <li>{@link #METADATA_KEY_COMMENT_SHORT_DESCRIPTION}</li> 677 * <li>{@link #METADATA_KEY_COMMENT_ACTUAL_TEXT}</li> 678 * <li>{@link #METADATA_KEY_COMMERCIAL}</li> 679 * <li>{@link #METADATA_KEY_HD_STATION_NAME_SHORT}</li> 680 * <li>{@link #METADATA_KEY_HD_STATION_NAME_LONG}</li> 681 * </ul> 682 * 683 * @param key The key for referencing this value 684 * @param value The String value to store 685 * @return the same Builder instance 686 */ putString(String key, String value)687 public Builder putString(String key, String value) { 688 if (!METADATA_KEYS_TYPE.containsKey(key) || 689 METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) { 690 throw new IllegalArgumentException("The " + key 691 + " key cannot be used to put a String"); 692 } 693 mBundle.putString(key, value); 694 return this; 695 } 696 697 /** 698 * Put an int value into the meta data. Custom keys may be used, but if 699 * the METADATA_KEYs defined in this class are used they may only be one 700 * of the following: 701 * <ul> 702 * <li>{@link #METADATA_KEY_RDS_PI}</li> 703 * <li>{@link #METADATA_KEY_RDS_PTY}</li> 704 * <li>{@link #METADATA_KEY_RBDS_PTY}</li> 705 * <li>{@link #METADATA_KEY_HD_SUBCHANNELS_AVAILABLE}</li> 706 * </ul> 707 * or any bitmap represented by its identifier. 708 * 709 * @param key The key for referencing this value 710 * @param value The int value to store 711 * @return the same Builder instance 712 */ putInt(String key, int value)713 public Builder putInt(String key, int value) { 714 RadioMetadata.putInt(mBundle, key, value); 715 return this; 716 } 717 718 /** 719 * Put a {@link Bitmap} into the meta data. Custom keys may be used, but 720 * if the METADATA_KEYs defined in this class are used they may only be 721 * one of the following: 722 * <ul> 723 * <li>{@link #METADATA_KEY_ICON}</li> 724 * <li>{@link #METADATA_KEY_ART}</li> 725 * </ul> 726 * <p> 727 * 728 * @param key The key for referencing this value 729 * @param value The Bitmap to store 730 * @return the same Builder instance 731 */ putBitmap(String key, Bitmap value)732 public Builder putBitmap(String key, Bitmap value) { 733 if (!METADATA_KEYS_TYPE.containsKey(key) || 734 METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) { 735 throw new IllegalArgumentException("The " + key 736 + " key cannot be used to put a Bitmap"); 737 } 738 mBundle.putParcelable(key, value); 739 return this; 740 } 741 742 /** 743 * Put a {@link RadioMetadata.Clock} into the meta data. Custom keys may be used, but if the 744 * METADATA_KEYs defined in this class are used they may only be one of the following: 745 * <ul> 746 * <li>{@link #METADATA_KEY_CLOCK}</li> 747 * </ul> 748 * 749 * @param utcSecondsSinceEpoch Number of seconds since epoch for UTC + 0 timezone. 750 * @param timezoneOffsetMinutes Offset of timezone from UTC + 0 in minutes. 751 * @return the same Builder instance. 752 */ putClock(String key, long utcSecondsSinceEpoch, int timezoneOffsetMinutes)753 public Builder putClock(String key, long utcSecondsSinceEpoch, int timezoneOffsetMinutes) { 754 if (!METADATA_KEYS_TYPE.containsKey(key) || 755 METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_CLOCK) { 756 throw new IllegalArgumentException("The " + key 757 + " key cannot be used to put a RadioMetadata.Clock."); 758 } 759 mBundle.putParcelable(key, new Clock(utcSecondsSinceEpoch, timezoneOffsetMinutes)); 760 return this; 761 } 762 763 /** 764 * Put a String array into the meta data. Custom keys may be used, but if 765 * the METADATA_KEYs defined in this class are used they may only be one 766 * of the following: 767 * <ul> 768 * <li>{@link #METADATA_KEY_UFIDS}</li> 769 * </ul> 770 * 771 * @param key The key for referencing this value 772 * @param value The String value to store 773 * @return the same Builder instance 774 * @throws NullPointerException if key or value is null 775 * @throws IllegalArgumentException if the key is not string-array-type key 776 */ 777 @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) 778 @NonNull putStringArray(@onNull String key, @NonNull String[] value)779 public Builder putStringArray(@NonNull String key, @NonNull String[] value) { 780 Objects.requireNonNull(key, "Key can not be null"); 781 Objects.requireNonNull(value, "Value can not be null"); 782 if (!METADATA_KEYS_TYPE.containsKey(key) 783 || !Objects.equals(METADATA_KEYS_TYPE.get(key), METADATA_TYPE_TEXT_ARRAY)) { 784 throw new IllegalArgumentException("The " + key 785 + " key cannot be used to put a RadioMetadata String Array."); 786 } 787 mBundle.putStringArray(key, value); 788 return this; 789 } 790 791 792 /** 793 * Creates a {@link RadioMetadata} instance with the specified fields. 794 * 795 * @return a new {@link RadioMetadata} object 796 */ build()797 public RadioMetadata build() { 798 return new RadioMetadata(mBundle); 799 } 800 scaleBitmap(Bitmap bmp, int maxSize)801 private Bitmap scaleBitmap(Bitmap bmp, int maxSize) { 802 float maxSizeF = maxSize; 803 float widthScale = maxSizeF / bmp.getWidth(); 804 float heightScale = maxSizeF / bmp.getHeight(); 805 float scale = Math.min(widthScale, heightScale); 806 int height = (int) (bmp.getHeight() * scale); 807 int width = (int) (bmp.getWidth() * scale); 808 return Bitmap.createScaledBitmap(bmp, width, height, true); 809 } 810 } 811 putIntFromNative(int nativeKey, int value)812 int putIntFromNative(int nativeKey, int value) { 813 String key = getKeyFromNativeKey(nativeKey); 814 try { 815 putInt(mBundle, key, value); 816 // Invalidate mHashCode to force it to be recomputed. 817 mHashCode = null; 818 return 0; 819 } catch (IllegalArgumentException ex) { 820 return -1; 821 } 822 } 823 putStringFromNative(int nativeKey, String value)824 int putStringFromNative(int nativeKey, String value) { 825 String key = getKeyFromNativeKey(nativeKey); 826 if (!METADATA_KEYS_TYPE.containsKey(key) || 827 METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) { 828 return -1; 829 } 830 mBundle.putString(key, value); 831 // Invalidate mHashCode to force it to be recomputed. 832 mHashCode = null; 833 return 0; 834 } 835 putBitmapFromNative(int nativeKey, byte[] value)836 int putBitmapFromNative(int nativeKey, byte[] value) { 837 String key = getKeyFromNativeKey(nativeKey); 838 if (!METADATA_KEYS_TYPE.containsKey(key) || 839 METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) { 840 return -1; 841 } 842 Bitmap bmp = null; 843 try { 844 bmp = BitmapFactory.decodeByteArray(value, 0, value.length); 845 if (bmp != null) { 846 mBundle.putParcelable(key, bmp); 847 // Invalidate mHashCode to force it to be recomputed. 848 mHashCode = null; 849 return 0; 850 } 851 } catch (Exception e) { 852 } 853 return -1; 854 } 855 putClockFromNative(int nativeKey, long utcEpochSeconds, int timezoneOffsetInMinutes)856 int putClockFromNative(int nativeKey, long utcEpochSeconds, int timezoneOffsetInMinutes) { 857 String key = getKeyFromNativeKey(nativeKey); 858 if (!METADATA_KEYS_TYPE.containsKey(key) || 859 METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_CLOCK) { 860 return -1; 861 } 862 mBundle.putParcelable(key, new RadioMetadata.Clock( 863 utcEpochSeconds, timezoneOffsetInMinutes)); 864 // Invalidate mHashCode to force it to be recomputed. 865 mHashCode = null; 866 return 0; 867 } 868 } 869