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