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.compat.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