1 /* 2 * Copyright (C) 2009 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 17 package android.media; 18 19 import android.annotation.UnsupportedAppUsage; 20 import android.os.Parcel; 21 import android.util.Log; 22 import android.util.MathUtils; 23 24 import java.util.Calendar; 25 import java.util.Collections; 26 import java.util.Date; 27 import java.util.HashMap; 28 import java.util.Set; 29 import java.util.TimeZone; 30 31 32 /** 33 Class to hold the media's metadata. Metadata are used 34 for human consumption and can be embedded in the media (e.g 35 shoutcast) or available from an external source. The source can be 36 local (e.g thumbnail stored in the DB) or remote. 37 38 Metadata is like a Bundle. It is sparse and each key can occur at 39 most once. The key is an integer and the value is the actual metadata. 40 41 The caller is expected to know the type of the metadata and call 42 the right get* method to fetch its value. 43 44 @hide 45 @deprecated Use {@link MediaMetadata}. 46 */ 47 @Deprecated public class Metadata 48 { 49 // The metadata are keyed using integers rather than more heavy 50 // weight strings. We considered using Bundle to ship the metadata 51 // between the native layer and the java layer but dropped that 52 // option since keeping in sync a native implementation of Bundle 53 // and the java one would be too burdensome. Besides Bundle uses 54 // String for its keys. 55 // The key range [0 8192) is reserved for the system. 56 // 57 // We manually serialize the data in Parcels. For large memory 58 // blob (bitmaps, raw pictures) we use MemoryFile which allow the 59 // client to make the data purge-able once it is done with it. 60 // 61 62 /** 63 * {@hide} 64 */ 65 public static final int ANY = 0; // Never used for metadata returned, only for filtering. 66 // Keep in sync with kAny in MediaPlayerService.cpp 67 68 // Playback capabilities. 69 /** 70 * Indicate whether the media can be paused 71 */ 72 @UnsupportedAppUsage 73 public static final int PAUSE_AVAILABLE = 1; // Boolean 74 /** 75 * Indicate whether the media can be backward seeked 76 */ 77 @UnsupportedAppUsage 78 public static final int SEEK_BACKWARD_AVAILABLE = 2; // Boolean 79 /** 80 * Indicate whether the media can be forward seeked 81 */ 82 @UnsupportedAppUsage 83 public static final int SEEK_FORWARD_AVAILABLE = 3; // Boolean 84 /** 85 * Indicate whether the media can be seeked 86 */ 87 @UnsupportedAppUsage 88 public static final int SEEK_AVAILABLE = 4; // Boolean 89 90 // TODO: Should we use numbers compatible with the metadata retriever? 91 /** 92 * {@hide} 93 */ 94 public static final int TITLE = 5; // String 95 /** 96 * {@hide} 97 */ 98 public static final int COMMENT = 6; // String 99 /** 100 * {@hide} 101 */ 102 public static final int COPYRIGHT = 7; // String 103 /** 104 * {@hide} 105 */ 106 public static final int ALBUM = 8; // String 107 /** 108 * {@hide} 109 */ 110 public static final int ARTIST = 9; // String 111 /** 112 * {@hide} 113 */ 114 public static final int AUTHOR = 10; // String 115 /** 116 * {@hide} 117 */ 118 public static final int COMPOSER = 11; // String 119 /** 120 * {@hide} 121 */ 122 public static final int GENRE = 12; // String 123 /** 124 * {@hide} 125 */ 126 public static final int DATE = 13; // Date 127 /** 128 * {@hide} 129 */ 130 public static final int DURATION = 14; // Integer(millisec) 131 /** 132 * {@hide} 133 */ 134 public static final int CD_TRACK_NUM = 15; // Integer 1-based 135 /** 136 * {@hide} 137 */ 138 public static final int CD_TRACK_MAX = 16; // Integer 139 /** 140 * {@hide} 141 */ 142 public static final int RATING = 17; // String 143 /** 144 * {@hide} 145 */ 146 public static final int ALBUM_ART = 18; // byte[] 147 /** 148 * {@hide} 149 */ 150 public static final int VIDEO_FRAME = 19; // Bitmap 151 152 /** 153 * {@hide} 154 */ 155 public static final int BIT_RATE = 20; // Integer, Aggregate rate of 156 // all the streams in bps. 157 158 /** 159 * {@hide} 160 */ 161 public static final int AUDIO_BIT_RATE = 21; // Integer, bps 162 /** 163 * {@hide} 164 */ 165 public static final int VIDEO_BIT_RATE = 22; // Integer, bps 166 /** 167 * {@hide} 168 */ 169 public static final int AUDIO_SAMPLE_RATE = 23; // Integer, Hz 170 /** 171 * {@hide} 172 */ 173 public static final int VIDEO_FRAME_RATE = 24; // Integer, Hz 174 175 // See RFC2046 and RFC4281. 176 /** 177 * {@hide} 178 */ 179 public static final int MIME_TYPE = 25; // String 180 /** 181 * {@hide} 182 */ 183 public static final int AUDIO_CODEC = 26; // String 184 /** 185 * {@hide} 186 */ 187 public static final int VIDEO_CODEC = 27; // String 188 189 /** 190 * {@hide} 191 */ 192 public static final int VIDEO_HEIGHT = 28; // Integer 193 /** 194 * {@hide} 195 */ 196 public static final int VIDEO_WIDTH = 29; // Integer 197 /** 198 * {@hide} 199 */ 200 public static final int NUM_TRACKS = 30; // Integer 201 /** 202 * {@hide} 203 */ 204 public static final int DRM_CRIPPLED = 31; // Boolean 205 206 private static final int LAST_SYSTEM = 31; 207 private static final int FIRST_CUSTOM = 8192; 208 209 // Shorthands to set the MediaPlayer's metadata filter. 210 /** 211 * {@hide} 212 */ 213 public static final Set<Integer> MATCH_NONE = Collections.EMPTY_SET; 214 /** 215 * {@hide} 216 */ 217 public static final Set<Integer> MATCH_ALL = Collections.singleton(ANY); 218 219 /** 220 * {@hide} 221 */ 222 public static final int STRING_VAL = 1; 223 /** 224 * {@hide} 225 */ 226 public static final int INTEGER_VAL = 2; 227 /** 228 * {@hide} 229 */ 230 public static final int BOOLEAN_VAL = 3; 231 /** 232 * {@hide} 233 */ 234 public static final int LONG_VAL = 4; 235 /** 236 * {@hide} 237 */ 238 public static final int DOUBLE_VAL = 5; 239 /** 240 * {@hide} 241 */ 242 public static final int DATE_VAL = 6; 243 /** 244 * {@hide} 245 */ 246 public static final int BYTE_ARRAY_VAL = 7; 247 // FIXME: misses a type for shared heap is missing (MemoryFile). 248 // FIXME: misses a type for bitmaps. 249 private static final int LAST_TYPE = 7; 250 251 private static final String TAG = "media.Metadata"; 252 private static final int kInt32Size = 4; 253 private static final int kMetaHeaderSize = 2 * kInt32Size; // size + marker 254 private static final int kRecordHeaderSize = 3 * kInt32Size; // size + id + type 255 256 private static final int kMetaMarker = 0x4d455441; // 'M' 'E' 'T' 'A' 257 258 // After a successful parsing, set the parcel with the serialized metadata. 259 private Parcel mParcel; 260 261 // Map to associate a Metadata key (e.g TITLE) with the offset of 262 // the record's payload in the parcel. 263 // Used to look up if a key was present too. 264 // Key: Metadata ID 265 // Value: Offset of the metadata type field in the record. 266 private final HashMap<Integer, Integer> mKeyToPosMap = 267 new HashMap<Integer, Integer>(); 268 269 /** 270 * {@hide} 271 */ 272 @UnsupportedAppUsage Metadata()273 public Metadata() { } 274 275 /** 276 * Go over all the records, collecting metadata keys and records' 277 * type field offset in the Parcel. These are stored in 278 * mKeyToPosMap for latter retrieval. 279 * Format of a metadata record: 280 <pre> 281 1 2 3 282 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 283 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 284 | record size | 285 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 286 | metadata key | // TITLE 287 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 288 | metadata type | // STRING_VAL 289 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 290 | | 291 | .... metadata payload .... | 292 | | 293 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 294 </pre> 295 * @param parcel With the serialized records. 296 * @param bytesLeft How many bytes in the parcel should be processed. 297 * @return false if an error occurred during parsing. 298 */ scanAllRecords(Parcel parcel, int bytesLeft)299 private boolean scanAllRecords(Parcel parcel, int bytesLeft) { 300 int recCount = 0; 301 boolean error = false; 302 303 mKeyToPosMap.clear(); 304 while (bytesLeft > kRecordHeaderSize) { 305 final int start = parcel.dataPosition(); 306 // Check the size. 307 final int size = parcel.readInt(); 308 309 if (size <= kRecordHeaderSize) { // at least 1 byte should be present. 310 Log.e(TAG, "Record is too short"); 311 error = true; 312 break; 313 } 314 315 // Check the metadata key. 316 final int metadataId = parcel.readInt(); 317 if (!checkMetadataId(metadataId)) { 318 error = true; 319 break; 320 } 321 322 // Store the record offset which points to the type 323 // field so we can later on read/unmarshall the record 324 // payload. 325 if (mKeyToPosMap.containsKey(metadataId)) { 326 Log.e(TAG, "Duplicate metadata ID found"); 327 error = true; 328 break; 329 } 330 331 mKeyToPosMap.put(metadataId, parcel.dataPosition()); 332 333 // Check the metadata type. 334 final int metadataType = parcel.readInt(); 335 if (metadataType <= 0 || metadataType > LAST_TYPE) { 336 Log.e(TAG, "Invalid metadata type " + metadataType); 337 error = true; 338 break; 339 } 340 341 // Skip to the next one. 342 try { 343 parcel.setDataPosition(MathUtils.addOrThrow(start, size)); 344 } catch (IllegalArgumentException e) { 345 Log.e(TAG, "Invalid size: " + e.getMessage()); 346 error = true; 347 break; 348 } 349 350 bytesLeft -= size; 351 ++recCount; 352 } 353 354 if (0 != bytesLeft || error) { 355 Log.e(TAG, "Ran out of data or error on record " + recCount); 356 mKeyToPosMap.clear(); 357 return false; 358 } else { 359 return true; 360 } 361 } 362 363 /** 364 * Check a parcel containing metadata is well formed. The header 365 * is checked as well as the individual records format. However, the 366 * data inside the record is not checked because we do lazy access 367 * (we check/unmarshall only data the user asks for.) 368 * 369 * Format of a metadata parcel: 370 <pre> 371 1 2 3 372 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 373 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 374 | metadata total size | 375 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 376 | 'M' | 'E' | 'T' | 'A' | 377 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 378 | | 379 | .... metadata records .... | 380 | | 381 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 382 </pre> 383 * 384 * @param parcel With the serialized data. Metadata keeps a 385 * reference on it to access it later on. The caller 386 * should not modify the parcel after this call (and 387 * not call recycle on it.) 388 * @return false if an error occurred. 389 * {@hide} 390 */ 391 @UnsupportedAppUsage parse(Parcel parcel)392 public boolean parse(Parcel parcel) { 393 if (parcel.dataAvail() < kMetaHeaderSize) { 394 Log.e(TAG, "Not enough data " + parcel.dataAvail()); 395 return false; 396 } 397 398 final int pin = parcel.dataPosition(); // to roll back in case of errors. 399 final int size = parcel.readInt(); 400 401 // The extra kInt32Size below is to account for the int32 'size' just read. 402 if (parcel.dataAvail() + kInt32Size < size || size < kMetaHeaderSize) { 403 Log.e(TAG, "Bad size " + size + " avail " + parcel.dataAvail() + " position " + pin); 404 parcel.setDataPosition(pin); 405 return false; 406 } 407 408 // Checks if the 'M' 'E' 'T' 'A' marker is present. 409 final int kShouldBeMetaMarker = parcel.readInt(); 410 if (kShouldBeMetaMarker != kMetaMarker ) { 411 Log.e(TAG, "Marker missing " + Integer.toHexString(kShouldBeMetaMarker)); 412 parcel.setDataPosition(pin); 413 return false; 414 } 415 416 // Scan the records to collect metadata ids and offsets. 417 if (!scanAllRecords(parcel, size - kMetaHeaderSize)) { 418 parcel.setDataPosition(pin); 419 return false; 420 } 421 mParcel = parcel; 422 return true; 423 } 424 425 /** 426 * @return The set of metadata ID found. 427 */ 428 @UnsupportedAppUsage keySet()429 public Set<Integer> keySet() { 430 return mKeyToPosMap.keySet(); 431 } 432 433 /** 434 * @return true if a value is present for the given key. 435 */ 436 @UnsupportedAppUsage has(final int metadataId)437 public boolean has(final int metadataId) { 438 if (!checkMetadataId(metadataId)) { 439 throw new IllegalArgumentException("Invalid key: " + metadataId); 440 } 441 return mKeyToPosMap.containsKey(metadataId); 442 } 443 444 // Accessors. 445 // Caller must make sure the key is present using the {@code has} 446 // method otherwise a RuntimeException will occur. 447 448 /** 449 * {@hide} 450 */ 451 @UnsupportedAppUsage getString(final int key)452 public String getString(final int key) { 453 checkType(key, STRING_VAL); 454 return mParcel.readString(); 455 } 456 457 /** 458 * {@hide} 459 */ 460 @UnsupportedAppUsage getInt(final int key)461 public int getInt(final int key) { 462 checkType(key, INTEGER_VAL); 463 return mParcel.readInt(); 464 } 465 466 /** 467 * Get the boolean value indicated by key 468 */ 469 @UnsupportedAppUsage getBoolean(final int key)470 public boolean getBoolean(final int key) { 471 checkType(key, BOOLEAN_VAL); 472 return mParcel.readInt() == 1; 473 } 474 475 /** 476 * {@hide} 477 */ 478 @UnsupportedAppUsage getLong(final int key)479 public long getLong(final int key) { 480 checkType(key, LONG_VAL); /** 481 * {@hide} 482 */ 483 return mParcel.readLong(); 484 } 485 486 /** 487 * {@hide} 488 */ 489 @UnsupportedAppUsage getDouble(final int key)490 public double getDouble(final int key) { 491 checkType(key, DOUBLE_VAL); 492 return mParcel.readDouble(); 493 } 494 495 /** 496 * {@hide} 497 */ 498 @UnsupportedAppUsage getByteArray(final int key)499 public byte[] getByteArray(final int key) { 500 checkType(key, BYTE_ARRAY_VAL); 501 return mParcel.createByteArray(); 502 } 503 504 /** 505 * {@hide} 506 */ 507 @UnsupportedAppUsage getDate(final int key)508 public Date getDate(final int key) { 509 checkType(key, DATE_VAL); 510 final long timeSinceEpoch = mParcel.readLong(); 511 final String timeZone = mParcel.readString(); 512 513 if (timeZone.length() == 0) { 514 return new Date(timeSinceEpoch); 515 } else { 516 TimeZone tz = TimeZone.getTimeZone(timeZone); 517 Calendar cal = Calendar.getInstance(tz); 518 519 cal.setTimeInMillis(timeSinceEpoch); 520 return cal.getTime(); 521 } 522 } 523 524 /** 525 * @return the last available system metadata id. Ids are 526 * 1-indexed. 527 * {@hide} 528 */ lastSytemId()529 public static int lastSytemId() { return LAST_SYSTEM; } 530 531 /** 532 * @return the first available cutom metadata id. 533 * {@hide} 534 */ firstCustomId()535 public static int firstCustomId() { return FIRST_CUSTOM; } 536 537 /** 538 * @return the last value of known type. Types are 1-indexed. 539 * {@hide} 540 */ lastType()541 public static int lastType() { return LAST_TYPE; } 542 543 /** 544 * Check val is either a system id or a custom one. 545 * @param val Metadata key to test. 546 * @return true if it is in a valid range. 547 **/ checkMetadataId(final int val)548 private boolean checkMetadataId(final int val) { 549 if (val <= ANY || (LAST_SYSTEM < val && val < FIRST_CUSTOM)) { 550 Log.e(TAG, "Invalid metadata ID " + val); 551 return false; 552 } 553 return true; 554 } 555 556 /** 557 * Check the type of the data match what is expected. 558 */ checkType(final int key, final int expectedType)559 private void checkType(final int key, final int expectedType) { 560 final int pos = mKeyToPosMap.get(key); 561 562 mParcel.setDataPosition(pos); 563 564 final int type = mParcel.readInt(); 565 if (type != expectedType) { 566 throw new IllegalStateException("Wrong type " + expectedType + " but got " + type); 567 } 568 } 569 } 570