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