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.os.Parcel;
20import android.util.Log;
21import android.util.MathUtils;
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   @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     */
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     */
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     */
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     */
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     */
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     */
442    public String getString(final int key) {
443        checkType(key, STRING_VAL);
444        return mParcel.readString();
445    }
446
447    /**
448     * {@hide}
449     */
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     */
458    public boolean getBoolean(final int key) {
459        checkType(key, BOOLEAN_VAL);
460        return mParcel.readInt() == 1;
461    }
462
463    /**
464     * {@hide}
465     */
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     */
476    public double getDouble(final int key) {
477        checkType(key, DOUBLE_VAL);
478        return mParcel.readDouble();
479    }
480
481    /**
482     * {@hide}
483     */
484    public byte[] getByteArray(final int key) {
485        checkType(key, BYTE_ARRAY_VAL);
486        return mParcel.createByteArray();
487    }
488
489    /**
490     * {@hide}
491     */
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     */
513    public static int lastSytemId() { return LAST_SYSTEM; }
514
515    /**
516     * @return the first available cutom metadata id.
517     * {@hide}
518     */
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     */
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     **/
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     */
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