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