Loading media/java/android/media/Metadata.java +188 −29 Original line number Diff line number Diff line Loading @@ -22,8 +22,8 @@ import android.util.Log; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.Set; import java.util.HashMap; /** Loading Loading @@ -54,7 +54,7 @@ public class Metadata // // We manually serialize the data in Parcels. For large memory // blob (bitmaps, raw pictures) we use MemoryFile which allow the // client to make the data purgeable once it is done with it. // client to make the data purge-able once it is done with it. // public static final int ANY = 0; // Never used for metadata returned, only for filtering. Loading Loading @@ -95,18 +95,34 @@ public class Metadata public static final int VIDEO_WIDTH = 26; // Integer public static final int NUM_TRACKS = 27; // Integer public static final int DRM_CRIPPLED = 28; // Boolean public static final int LAST_SYSTEM = 29; public static final int FIRST_CUSTOM = 8092; private static final int LAST_SYSTEM = 28; private static final int FIRST_CUSTOM = 8092; // Shorthands to set the MediaPlayer's metadata filter. public static final Set<Integer> MATCH_NONE = Collections.EMPTY_SET; public static final Set<Integer> MATCH_ALL = Collections.singleton(ANY); private static final int STRING_VAL = 1; private static final int INTEGER_VAL = 2; private static final int LONG_VAL = 3; private static final int DOUBLE_VAL = 4; private static final int TIMED_TEXT_VAL = 2; public static final int STRING_VAL = 1; public static final int INTEGER_VAL = 2; public static final int LONG_VAL = 3; public static final int DOUBLE_VAL = 4; public static final int TIMED_TEXT_VAL = 5; private static final int LAST_TYPE = 5; private static final String TAG = "media.Metadata"; private static final int kMetaHeaderSize = 8; // 8 bytes for the size + the marker private static final int kMetaMarker = 0x4d455441; // 'M' 'E' 'T' 'A' private static final int kRecordHeaderSize = 12; // size + id + type // After a successful parsing, set the parcel with the serialized metadata. private Parcel mParcel; // Map to associate a Metadata key (e.g TITLE) with the offset of // the record's payload in the parcel. // Used to look up if a key was present too. // Key: Metadata ID // Value: Offset of the metadata type field in the record. private final HashMap<Integer, Integer> mKeyToPosMap = new HashMap<Integer, Integer>(); /** * Helper class to hold a pair (time, text). Can be used to implement caption. Loading @@ -125,41 +141,163 @@ public class Metadata } } /* package */ Metadata() {} public Metadata() { } /* package */ boolean parse(Parcel data) { // FIXME: Implement. /** * Go over all the records, collecting metadata keys and records' * type field offset in the Parcel. These are stored in * mKeyToPosMap for latter retrieval. * Format of a metadata record: <pre> 1 2 3 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | record size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | metadata key | // TITLE +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | metadata type | // STRING_VAL +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | .... metadata payload .... | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ </pre> * @param parcel With the serialized records. * @param bytesLeft How many bytes in the parcel should be processed. * @return false if an error occurred during parsing. */ private boolean scanAllRecords(Parcel parcel, int bytesLeft) { int recCount = 0; boolean error = false; mKeyToPosMap.clear(); while (bytesLeft > kRecordHeaderSize) { final int start = parcel.dataPosition(); // Check the size. final int size = parcel.readInt(); if (size <= kRecordHeaderSize) { // at least 1 byte should be present. Log.e(TAG, "Record is too short"); error = true; break; } // Check the metadata key. final int metadataId = parcel.readInt(); if (!checkMetadataId(metadataId)) { error = true; break; } // Store the record offset which points to the type // field so we can later on read/unmarshall the record // payload. if (mKeyToPosMap.containsKey(metadataId)) { Log.e(TAG, "Duplicate metadata ID found"); error = true; break; } mKeyToPosMap.put(metadataId, parcel.dataPosition()); // Check the metadata type. final int metadataType = parcel.readInt(); if (metadataType <= 0 || metadataType > LAST_TYPE) { Log.e(TAG, "Invalid metadata type " + metadataType); error = true; break; } // Skip to the next one. parcel.setDataPosition(start + size); bytesLeft -= size; ++recCount; } if (0 != bytesLeft || error) { Log.e(TAG, "Ran out of data or error on record " + recCount); mKeyToPosMap.clear(); return false; } else { return true; } } /** * @return the number of element in this metadata set. * Check a parcel containing metadata is well formed. The header * is checked as well as the individual records format. However, the * data inside the record is not checked because we do lazy access * (we check/unmarshall only data the user asks for.) * * Format of a metadata parcel: <pre> 1 2 3 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | metadata total size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 'M' | 'E' | 'T' | 'A' | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | .... metadata records .... | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ </pre> * * @param parcel With the serialized data. Metadata keeps a * reference on it to access it later on. The caller * should not modify the parcel after this call (and * not call recycle on it.) * @return false if an error occurred. */ public int size() { // FIXME: Implement. return 0; public boolean parse(Parcel parcel) { if (parcel.dataAvail() < kMetaHeaderSize) { Log.e(TAG, "Not enough data " + parcel.dataAvail()); return false; } final int pin = parcel.dataPosition(); // to roll back in case of errors. final int size = parcel.readInt(); if (parcel.dataAvail() < size || size < kMetaHeaderSize) { Log.e(TAG, "Bad size " + size); parcel.setDataPosition(pin); return false; } // Checks if the 'M' 'E' 'T' 'A' marker is present. final int kShouldBeMetaMarker = parcel.readInt(); if (kShouldBeMetaMarker != kMetaMarker ) { Log.e(TAG, "Marker missing " + Integer.toHexString(kShouldBeMetaMarker)); parcel.setDataPosition(pin); return false; } // Scan the records to collect metadata ids and offsets. if (!scanAllRecords(parcel, size - kMetaHeaderSize)) { parcel.setDataPosition(pin); return false; } mParcel = parcel; return true; } /** * @return an iterator over the keys. * @return The set of metadata ID found. */ public Iterator<Integer> iterator() { // FIXME: Implement. return new java.util.HashSet<Integer>().iterator(); public Set<Integer> keySet() { return mKeyToPosMap.keySet(); } /** * @return true if a value is present for the given key. */ public boolean has(final int key) { if (key <= ANY) { throw new IllegalArgumentException("Invalid key: " + key); } if (LAST_SYSTEM <= key && key < FIRST_CUSTOM) { throw new IllegalArgumentException("Key in reserved range: " + key); public boolean has(final int metadataId) { if (!checkMetadataId(metadataId)) { throw new IllegalArgumentException("Invalid key: " + metadataId); } // FIXME: Implement. return true; return mKeyToPosMap.containsKey(metadataId); } // Accessors Loading Loading @@ -201,4 +339,25 @@ public class Metadata // FIXME: Implement. return new TimedText(new Date(0), "<missing>"); } // @return the last available system metadata id. Ids are // 1-indexed. public static int lastSytemId() { return LAST_SYSTEM; } // @return the first available cutom metadata id. public static int firstCustomId() { return FIRST_CUSTOM; } // @return the last value of known type. Types are 1-indexed. public static int lastType() { return LAST_TYPE; } // Check val is either a system id or a custom one. // @param val Metadata key to test. // @return true if it is in a valid range. private boolean checkMetadataId(final int val) { if (val <= ANY || (LAST_SYSTEM < val && val < FIRST_CUSTOM)) { Log.e(TAG, "Invalid metadata ID " + val); return false; } return true; } } media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java +7 −6 Original line number Diff line number Diff line Loading @@ -87,5 +87,6 @@ public class MediaFrameworkUnitTestRunner extends InstrumentationTestRunner { suite.addTestSuite(MediaPlayerSetLoopingStateUnitTest.class); suite.addTestSuite(MediaPlayerSetAudioStreamTypeStateUnitTest.class); suite.addTestSuite(MediaPlayerSetVolumeStateUnitTest.class); suite.addTestSuite(MediaPlayerMetadataParserTest.class); } } media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerMetadataParserTest.java 0 → 100644 +226 −0 Original line number Diff line number Diff line /* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.mediaframeworktest.unit; import android.media.Metadata; import android.os.Parcel; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; /* * Check the Java layer that parses serialized metadata in Parcel * works as expected. * */ public class MediaPlayerMetadataParserTest extends AndroidTestCase { private static final String TAG = "MediaPlayerMetadataTest"; private static final int kToken = 0xdeadbeef; private static final int kMarker = 0x4d455441; // 'M' 'E' 'T' 'A' private static final int kHeaderSize = 8; private Metadata mMetadata = null; private Parcel mParcel = null; @Override protected void setUp() throws Exception { super.setUp(); mMetadata = new Metadata(); mParcel = Parcel.obtain(); resetParcel(); } // Check parsing of the parcel fails. Make sure the parser rewind // the parcel properly. private void assertParseFail() throws Exception { mParcel.setDataPosition(0); assertFalse(mMetadata.parse(mParcel)); assertEquals(0, mParcel.dataPosition()); } // Check parsing of the parcel is successful. Before the // invocation of the parser a token is inserted. When the parser // returns, the parcel should be positioned at the token (check it // does not read too much data). private void assertParse() throws Exception { mParcel.writeInt(kToken); mParcel.setDataPosition(0); assertTrue(mMetadata.parse(mParcel)); assertEquals(kToken, mParcel.readInt()); } // Write the number of bytes from the start of the parcel to the // current position at the beginning of the parcel (offset 0). private void adjustSize() { adjustSize(0); } // Write the number of bytes from the offset to the current // position at position pointed by offset. private void adjustSize(int offset) { final int pos = mParcel.dataPosition(); mParcel.setDataPosition(offset); mParcel.writeInt(pos - offset); mParcel.setDataPosition(pos); } // Rewind the parcel and insert the header. private void resetParcel() { mParcel.setDataPosition(0); // Most tests will use a properly formed parcel with a size // and the meta marker so we add them by default. mParcel.writeInt(-1); // Placeholder for the size mParcel.writeInt(kMarker); } // Insert a string record at the current position. private void writeStringRecord(int metadataId, String val) { final int start = mParcel.dataPosition(); mParcel.writeInt(-1); // Placeholder for the length mParcel.writeInt(metadataId); mParcel.writeInt(Metadata.STRING_VAL); mParcel.writeString(val); adjustSize(start); } // ---------------------------------------------------------------------- // START OF THE TESTS // There should be at least 8 bytes in the parcel, 4 for the size // and 4 for the 'M' 'E' 'T' 'A' marker. @SmallTest public void testMissingSizeAndMarker() throws Exception { for (int i = 0; i < kHeaderSize; ++i) { mParcel.setDataPosition(0); mParcel.setDataSize(i); assertEquals(i, mParcel.dataAvail()); assertParseFail(); } } // There should be at least 'size' bytes in the parcel. @SmallTest public void testMissingData() throws Exception { final int size = 20; mParcel.writeInt(size); mParcel.setDataSize(size - 1); assertParseFail(); } // Empty parcel is fine @SmallTest public void testEmptyIsOk() throws Exception { adjustSize(); assertParse(); } // RECORDS // A record header should be at least 12 bytes long @SmallTest public void testRecordMissingId() throws Exception { mParcel.writeInt(13); // record length // misses metadata id and metadata type. adjustSize(); assertParseFail(); } @SmallTest public void testRecordMissingType() throws Exception { mParcel.writeInt(13); // record length lies mParcel.writeInt(Metadata.TITLE); // misses metadata type adjustSize(); assertParseFail(); } @SmallTest public void testRecordWithZeroPayload() throws Exception { mParcel.writeInt(0); adjustSize(); assertParseFail(); } // A record cannot be empty. @SmallTest public void testRecordMissingPayload() throws Exception { mParcel.writeInt(12); mParcel.writeInt(Metadata.TITLE); mParcel.writeInt(Metadata.STRING_VAL); // misses payload adjustSize(); assertParseFail(); } // Check records can be found. @SmallTest public void testRecordsFound() throws Exception { writeStringRecord(Metadata.TITLE, "a title"); writeStringRecord(Metadata.GENRE, "comedy"); writeStringRecord(Metadata.firstCustomId(), "custom"); adjustSize(); assertParse(); assertTrue(mMetadata.has(Metadata.TITLE)); assertTrue(mMetadata.has(Metadata.GENRE)); assertTrue(mMetadata.has(Metadata.firstCustomId())); assertFalse(mMetadata.has(Metadata.DRM_CRIPPLED)); assertEquals(3, mMetadata.keySet().size()); } // Detects bad metadata type @SmallTest public void testBadMetadataType() throws Exception { final int start = mParcel.dataPosition(); mParcel.writeInt(-1); // Placeholder for the length mParcel.writeInt(Metadata.TITLE); mParcel.writeInt(0); // Invalid type. mParcel.writeString("dummy"); adjustSize(start); adjustSize(); assertParseFail(); } // Check a Metadata instance can be reused, i.e the parse method // wipes out the existing states/keys. @SmallTest public void testParseClearState() throws Exception { writeStringRecord(Metadata.TITLE, "a title"); writeStringRecord(Metadata.GENRE, "comedy"); writeStringRecord(Metadata.firstCustomId(), "custom"); adjustSize(); assertParse(); resetParcel(); writeStringRecord(Metadata.MIME_TYPE, "audio/mpg"); adjustSize(); assertParse(); // Only the mime type metadata should be present. assertEquals(1, mMetadata.keySet().size()); assertTrue(mMetadata.has(Metadata.MIME_TYPE)); assertFalse(mMetadata.has(Metadata.TITLE)); assertFalse(mMetadata.has(Metadata.GENRE)); assertFalse(mMetadata.has(Metadata.firstCustomId())); } } Loading
media/java/android/media/Metadata.java +188 −29 Original line number Diff line number Diff line Loading @@ -22,8 +22,8 @@ import android.util.Log; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.Set; import java.util.HashMap; /** Loading Loading @@ -54,7 +54,7 @@ public class Metadata // // We manually serialize the data in Parcels. For large memory // blob (bitmaps, raw pictures) we use MemoryFile which allow the // client to make the data purgeable once it is done with it. // client to make the data purge-able once it is done with it. // public static final int ANY = 0; // Never used for metadata returned, only for filtering. Loading Loading @@ -95,18 +95,34 @@ public class Metadata public static final int VIDEO_WIDTH = 26; // Integer public static final int NUM_TRACKS = 27; // Integer public static final int DRM_CRIPPLED = 28; // Boolean public static final int LAST_SYSTEM = 29; public static final int FIRST_CUSTOM = 8092; private static final int LAST_SYSTEM = 28; private static final int FIRST_CUSTOM = 8092; // Shorthands to set the MediaPlayer's metadata filter. public static final Set<Integer> MATCH_NONE = Collections.EMPTY_SET; public static final Set<Integer> MATCH_ALL = Collections.singleton(ANY); private static final int STRING_VAL = 1; private static final int INTEGER_VAL = 2; private static final int LONG_VAL = 3; private static final int DOUBLE_VAL = 4; private static final int TIMED_TEXT_VAL = 2; public static final int STRING_VAL = 1; public static final int INTEGER_VAL = 2; public static final int LONG_VAL = 3; public static final int DOUBLE_VAL = 4; public static final int TIMED_TEXT_VAL = 5; private static final int LAST_TYPE = 5; private static final String TAG = "media.Metadata"; private static final int kMetaHeaderSize = 8; // 8 bytes for the size + the marker private static final int kMetaMarker = 0x4d455441; // 'M' 'E' 'T' 'A' private static final int kRecordHeaderSize = 12; // size + id + type // After a successful parsing, set the parcel with the serialized metadata. private Parcel mParcel; // Map to associate a Metadata key (e.g TITLE) with the offset of // the record's payload in the parcel. // Used to look up if a key was present too. // Key: Metadata ID // Value: Offset of the metadata type field in the record. private final HashMap<Integer, Integer> mKeyToPosMap = new HashMap<Integer, Integer>(); /** * Helper class to hold a pair (time, text). Can be used to implement caption. Loading @@ -125,41 +141,163 @@ public class Metadata } } /* package */ Metadata() {} public Metadata() { } /* package */ boolean parse(Parcel data) { // FIXME: Implement. /** * Go over all the records, collecting metadata keys and records' * type field offset in the Parcel. These are stored in * mKeyToPosMap for latter retrieval. * Format of a metadata record: <pre> 1 2 3 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | record size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | metadata key | // TITLE +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | metadata type | // STRING_VAL +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | .... metadata payload .... | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ </pre> * @param parcel With the serialized records. * @param bytesLeft How many bytes in the parcel should be processed. * @return false if an error occurred during parsing. */ private boolean scanAllRecords(Parcel parcel, int bytesLeft) { int recCount = 0; boolean error = false; mKeyToPosMap.clear(); while (bytesLeft > kRecordHeaderSize) { final int start = parcel.dataPosition(); // Check the size. final int size = parcel.readInt(); if (size <= kRecordHeaderSize) { // at least 1 byte should be present. Log.e(TAG, "Record is too short"); error = true; break; } // Check the metadata key. final int metadataId = parcel.readInt(); if (!checkMetadataId(metadataId)) { error = true; break; } // Store the record offset which points to the type // field so we can later on read/unmarshall the record // payload. if (mKeyToPosMap.containsKey(metadataId)) { Log.e(TAG, "Duplicate metadata ID found"); error = true; break; } mKeyToPosMap.put(metadataId, parcel.dataPosition()); // Check the metadata type. final int metadataType = parcel.readInt(); if (metadataType <= 0 || metadataType > LAST_TYPE) { Log.e(TAG, "Invalid metadata type " + metadataType); error = true; break; } // Skip to the next one. parcel.setDataPosition(start + size); bytesLeft -= size; ++recCount; } if (0 != bytesLeft || error) { Log.e(TAG, "Ran out of data or error on record " + recCount); mKeyToPosMap.clear(); return false; } else { return true; } } /** * @return the number of element in this metadata set. * Check a parcel containing metadata is well formed. The header * is checked as well as the individual records format. However, the * data inside the record is not checked because we do lazy access * (we check/unmarshall only data the user asks for.) * * Format of a metadata parcel: <pre> 1 2 3 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | metadata total size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 'M' | 'E' | 'T' | 'A' | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | .... metadata records .... | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ </pre> * * @param parcel With the serialized data. Metadata keeps a * reference on it to access it later on. The caller * should not modify the parcel after this call (and * not call recycle on it.) * @return false if an error occurred. */ public int size() { // FIXME: Implement. return 0; public boolean parse(Parcel parcel) { if (parcel.dataAvail() < kMetaHeaderSize) { Log.e(TAG, "Not enough data " + parcel.dataAvail()); return false; } final int pin = parcel.dataPosition(); // to roll back in case of errors. final int size = parcel.readInt(); if (parcel.dataAvail() < size || size < kMetaHeaderSize) { Log.e(TAG, "Bad size " + size); parcel.setDataPosition(pin); return false; } // Checks if the 'M' 'E' 'T' 'A' marker is present. final int kShouldBeMetaMarker = parcel.readInt(); if (kShouldBeMetaMarker != kMetaMarker ) { Log.e(TAG, "Marker missing " + Integer.toHexString(kShouldBeMetaMarker)); parcel.setDataPosition(pin); return false; } // Scan the records to collect metadata ids and offsets. if (!scanAllRecords(parcel, size - kMetaHeaderSize)) { parcel.setDataPosition(pin); return false; } mParcel = parcel; return true; } /** * @return an iterator over the keys. * @return The set of metadata ID found. */ public Iterator<Integer> iterator() { // FIXME: Implement. return new java.util.HashSet<Integer>().iterator(); public Set<Integer> keySet() { return mKeyToPosMap.keySet(); } /** * @return true if a value is present for the given key. */ public boolean has(final int key) { if (key <= ANY) { throw new IllegalArgumentException("Invalid key: " + key); } if (LAST_SYSTEM <= key && key < FIRST_CUSTOM) { throw new IllegalArgumentException("Key in reserved range: " + key); public boolean has(final int metadataId) { if (!checkMetadataId(metadataId)) { throw new IllegalArgumentException("Invalid key: " + metadataId); } // FIXME: Implement. return true; return mKeyToPosMap.containsKey(metadataId); } // Accessors Loading Loading @@ -201,4 +339,25 @@ public class Metadata // FIXME: Implement. return new TimedText(new Date(0), "<missing>"); } // @return the last available system metadata id. Ids are // 1-indexed. public static int lastSytemId() { return LAST_SYSTEM; } // @return the first available cutom metadata id. public static int firstCustomId() { return FIRST_CUSTOM; } // @return the last value of known type. Types are 1-indexed. public static int lastType() { return LAST_TYPE; } // Check val is either a system id or a custom one. // @param val Metadata key to test. // @return true if it is in a valid range. private boolean checkMetadataId(final int val) { if (val <= ANY || (LAST_SYSTEM < val && val < FIRST_CUSTOM)) { Log.e(TAG, "Invalid metadata ID " + val); return false; } return true; } }
media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java +7 −6 Original line number Diff line number Diff line Loading @@ -87,5 +87,6 @@ public class MediaFrameworkUnitTestRunner extends InstrumentationTestRunner { suite.addTestSuite(MediaPlayerSetLoopingStateUnitTest.class); suite.addTestSuite(MediaPlayerSetAudioStreamTypeStateUnitTest.class); suite.addTestSuite(MediaPlayerSetVolumeStateUnitTest.class); suite.addTestSuite(MediaPlayerMetadataParserTest.class); } }
media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerMetadataParserTest.java 0 → 100644 +226 −0 Original line number Diff line number Diff line /* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.mediaframeworktest.unit; import android.media.Metadata; import android.os.Parcel; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; /* * Check the Java layer that parses serialized metadata in Parcel * works as expected. * */ public class MediaPlayerMetadataParserTest extends AndroidTestCase { private static final String TAG = "MediaPlayerMetadataTest"; private static final int kToken = 0xdeadbeef; private static final int kMarker = 0x4d455441; // 'M' 'E' 'T' 'A' private static final int kHeaderSize = 8; private Metadata mMetadata = null; private Parcel mParcel = null; @Override protected void setUp() throws Exception { super.setUp(); mMetadata = new Metadata(); mParcel = Parcel.obtain(); resetParcel(); } // Check parsing of the parcel fails. Make sure the parser rewind // the parcel properly. private void assertParseFail() throws Exception { mParcel.setDataPosition(0); assertFalse(mMetadata.parse(mParcel)); assertEquals(0, mParcel.dataPosition()); } // Check parsing of the parcel is successful. Before the // invocation of the parser a token is inserted. When the parser // returns, the parcel should be positioned at the token (check it // does not read too much data). private void assertParse() throws Exception { mParcel.writeInt(kToken); mParcel.setDataPosition(0); assertTrue(mMetadata.parse(mParcel)); assertEquals(kToken, mParcel.readInt()); } // Write the number of bytes from the start of the parcel to the // current position at the beginning of the parcel (offset 0). private void adjustSize() { adjustSize(0); } // Write the number of bytes from the offset to the current // position at position pointed by offset. private void adjustSize(int offset) { final int pos = mParcel.dataPosition(); mParcel.setDataPosition(offset); mParcel.writeInt(pos - offset); mParcel.setDataPosition(pos); } // Rewind the parcel and insert the header. private void resetParcel() { mParcel.setDataPosition(0); // Most tests will use a properly formed parcel with a size // and the meta marker so we add them by default. mParcel.writeInt(-1); // Placeholder for the size mParcel.writeInt(kMarker); } // Insert a string record at the current position. private void writeStringRecord(int metadataId, String val) { final int start = mParcel.dataPosition(); mParcel.writeInt(-1); // Placeholder for the length mParcel.writeInt(metadataId); mParcel.writeInt(Metadata.STRING_VAL); mParcel.writeString(val); adjustSize(start); } // ---------------------------------------------------------------------- // START OF THE TESTS // There should be at least 8 bytes in the parcel, 4 for the size // and 4 for the 'M' 'E' 'T' 'A' marker. @SmallTest public void testMissingSizeAndMarker() throws Exception { for (int i = 0; i < kHeaderSize; ++i) { mParcel.setDataPosition(0); mParcel.setDataSize(i); assertEquals(i, mParcel.dataAvail()); assertParseFail(); } } // There should be at least 'size' bytes in the parcel. @SmallTest public void testMissingData() throws Exception { final int size = 20; mParcel.writeInt(size); mParcel.setDataSize(size - 1); assertParseFail(); } // Empty parcel is fine @SmallTest public void testEmptyIsOk() throws Exception { adjustSize(); assertParse(); } // RECORDS // A record header should be at least 12 bytes long @SmallTest public void testRecordMissingId() throws Exception { mParcel.writeInt(13); // record length // misses metadata id and metadata type. adjustSize(); assertParseFail(); } @SmallTest public void testRecordMissingType() throws Exception { mParcel.writeInt(13); // record length lies mParcel.writeInt(Metadata.TITLE); // misses metadata type adjustSize(); assertParseFail(); } @SmallTest public void testRecordWithZeroPayload() throws Exception { mParcel.writeInt(0); adjustSize(); assertParseFail(); } // A record cannot be empty. @SmallTest public void testRecordMissingPayload() throws Exception { mParcel.writeInt(12); mParcel.writeInt(Metadata.TITLE); mParcel.writeInt(Metadata.STRING_VAL); // misses payload adjustSize(); assertParseFail(); } // Check records can be found. @SmallTest public void testRecordsFound() throws Exception { writeStringRecord(Metadata.TITLE, "a title"); writeStringRecord(Metadata.GENRE, "comedy"); writeStringRecord(Metadata.firstCustomId(), "custom"); adjustSize(); assertParse(); assertTrue(mMetadata.has(Metadata.TITLE)); assertTrue(mMetadata.has(Metadata.GENRE)); assertTrue(mMetadata.has(Metadata.firstCustomId())); assertFalse(mMetadata.has(Metadata.DRM_CRIPPLED)); assertEquals(3, mMetadata.keySet().size()); } // Detects bad metadata type @SmallTest public void testBadMetadataType() throws Exception { final int start = mParcel.dataPosition(); mParcel.writeInt(-1); // Placeholder for the length mParcel.writeInt(Metadata.TITLE); mParcel.writeInt(0); // Invalid type. mParcel.writeString("dummy"); adjustSize(start); adjustSize(); assertParseFail(); } // Check a Metadata instance can be reused, i.e the parse method // wipes out the existing states/keys. @SmallTest public void testParseClearState() throws Exception { writeStringRecord(Metadata.TITLE, "a title"); writeStringRecord(Metadata.GENRE, "comedy"); writeStringRecord(Metadata.firstCustomId(), "custom"); adjustSize(); assertParse(); resetParcel(); writeStringRecord(Metadata.MIME_TYPE, "audio/mpg"); adjustSize(); assertParse(); // Only the mime type metadata should be present. assertEquals(1, mMetadata.keySet().size()); assertTrue(mMetadata.has(Metadata.MIME_TYPE)); assertFalse(mMetadata.has(Metadata.TITLE)); assertFalse(mMetadata.has(Metadata.GENRE)); assertFalse(mMetadata.has(Metadata.firstCustomId())); } }