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 
22 import java.util.Calendar;
23 import java.util.Collections;
24 import java.util.Date;
25 import java.util.HashMap;
26 import java.util.Set;
27 import java.util.TimeZone;
28 
29 
30 /**
31    Class to hold the media's metadata.  Metadata are used
32    for human consumption and can be embedded in the media (e.g
33    shoutcast) or available from an external source. The source can be
34    local (e.g thumbnail stored in the DB) or remote.
35 
36    Metadata is like a Bundle. It is sparse and each key can occur at
37    most once. The key is an integer and the value is the actual metadata.
38 
39    The caller is expected to know the type of the metadata and call
40    the right get* method to fetch its value.
41 
42    @hide
43    @deprecated Use {@link MediaMetadata}.
44  */
45 @Deprecated public 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      */
Metadata()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      */
scanAllRecords(Parcel parcel, int bytesLeft)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      */
parse(Parcel parcel)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      */
keySet()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      */
has(final int metadataId)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      */
getString(final int key)434     public String getString(final int key) {
435         checkType(key, STRING_VAL);
436         return mParcel.readString();
437     }
438 
439     /**
440      * {@hide}
441      */
getInt(final int key)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      */
getBoolean(final int key)450     public boolean getBoolean(final int key) {
451         checkType(key, BOOLEAN_VAL);
452         return mParcel.readInt() == 1;
453     }
454 
455     /**
456      * {@hide}
457      */
getLong(final int key)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      */
getDouble(final int key)468     public double getDouble(final int key) {
469         checkType(key, DOUBLE_VAL);
470         return mParcel.readDouble();
471     }
472 
473     /**
474      * {@hide}
475      */
getByteArray(final int key)476     public byte[] getByteArray(final int key) {
477         checkType(key, BYTE_ARRAY_VAL);
478         return mParcel.createByteArray();
479     }
480 
481     /**
482      * {@hide}
483      */
getDate(final int key)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      */
lastSytemId()505     public static int lastSytemId() { return LAST_SYSTEM; }
506 
507     /**
508      * @return the first available cutom metadata id.
509      * {@hide}
510      */
firstCustomId()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      */
lastType()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      **/
checkMetadataId(final int val)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      */
checkType(final int key, final int expectedType)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