/* * Copyright (C) 2011 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 android.media; import android.os.Parcel; import android.util.Log; import java.util.HashMap; import java.util.Set; import java.util.List; import java.util.ArrayList; /** * Class to hold the timed text's metadata. * * {@hide} */ public class TimedText { private static final int FIRST_PUBLIC_KEY = 1; // These keys must be in sync with the keys in TextDescription.h public static final int KEY_DISPLAY_FLAGS = 1; // int public static final int KEY_STYLE_FLAGS = 2; // int public static final int KEY_BACKGROUND_COLOR_RGBA = 3; // int public static final int KEY_HIGHLIGHT_COLOR_RGBA = 4; // int public static final int KEY_SCROLL_DELAY = 5; // int public static final int KEY_WRAP_TEXT = 6; // int public static final int KEY_START_TIME = 7; // int public static final int KEY_STRUCT_BLINKING_TEXT_LIST = 8; // List<CharPos> public static final int KEY_STRUCT_FONT_LIST = 9; // List<Font> public static final int KEY_STRUCT_HIGHLIGHT_LIST = 10; // List<CharPos> public static final int KEY_STRUCT_HYPER_TEXT_LIST = 11; // List<HyperText> public static final int KEY_STRUCT_KARAOKE_LIST = 12; // List<Karaoke> public static final int KEY_STRUCT_STYLE_LIST = 13; // List<Style> public static final int KEY_STRUCT_TEXT_POS = 14; // TextPos public static final int KEY_STRUCT_JUSTIFICATION = 15; // Justification public static final int KEY_STRUCT_TEXT = 16; // Text private static final int LAST_PUBLIC_KEY = 16; private static final int FIRST_PRIVATE_KEY = 101; // The following keys are used between TimedText.java and // TextDescription.cpp in order to parce the Parcel. private static final int KEY_GLOBAL_SETTING = 101; private static final int KEY_LOCAL_SETTING = 102; private static final int KEY_START_CHAR = 103; private static final int KEY_END_CHAR = 104; private static final int KEY_FONT_ID = 105; private static final int KEY_FONT_SIZE = 106; private static final int KEY_TEXT_COLOR_RGBA = 107; private static final int LAST_PRIVATE_KEY = 107; private static final String TAG = "TimedText"; private Parcel mParcel = Parcel.obtain(); private final HashMap<Integer, Object> mKeyObjectMap = new HashMap<Integer, Object>(); private int mDisplayFlags = -1; private int mBackgroundColorRGBA = -1; private int mHighlightColorRGBA = -1; private int mScrollDelay = -1; private int mWrapText = -1; private List<CharPos> mBlinkingPosList = null; private List<CharPos> mHighlightPosList = null; private List<Karaoke> mKaraokeList = null; private List<Font> mFontList = null; private List<Style> mStyleList = null; private List<HyperText> mHyperTextList = null; private TextPos mTextPos; private Justification mJustification; private Text mTextStruct; /** * Helper class to hold the text length and text content of * one text sample. The member variables in this class are * read-only. */ public class Text { /** * The byte-count of this text sample */ public int textLen; /** * The text sample */ public byte[] text; public Text() { } } /** * Helper class to hold the start char offset and end char offset * for Blinking Text or Highlight Text. endChar is the end offset * of the text (startChar + number of characters to be highlighted * or blinked). The member variables in this class are read-only. */ public class CharPos { /** * The offset of the start character */ public int startChar = -1; /** * The offset of the end character */ public int endChar = -1; public CharPos() { } } /** * Helper class to hold the box position to display the text sample. * The member variables in this class are read-only. */ public class TextPos { /** * The top position of the text */ public int top = -1; /** * The left position of the text */ public int left = -1; /** * The bottom position of the text */ public int bottom = -1; /** * The right position of the text */ public int right = -1; public TextPos() { } } /** * Helper class to hold the justification for text display in the text box. * The member variables in this class are read-only. */ public class Justification { /** * horizontalJustification 0: left, 1: centered, -1: right */ public int horizontalJustification = -1; /** * verticalJustification 0: top, 1: centered, -1: bottom */ public int verticalJustification = -1; public Justification() { } } /** * Helper class to hold the style information to display the text. * The member variables in this class are read-only. */ public class Style { /** * The offset of the start character which applys this style */ public int startChar = -1; /** * The offset of the end character which applys this style */ public int endChar = -1; /** * ID of the font. This ID will be used to choose the font * to be used from the font list. */ public int fontID = -1; /** * True if the characters should be bold */ public boolean isBold = false; /** * True if the characters should be italic */ public boolean isItalic = false; /** * True if the characters should be underlined */ public boolean isUnderlined = false; /** * The size of the font */ public int fontSize = -1; /** * To specify the RGBA color: 8 bits each of red, green, blue, * and an alpha(transparency) value */ public int colorRGBA = -1; public Style() { } } /** * Helper class to hold the font ID and name. * The member variables in this class are read-only. */ public class Font { /** * The font ID */ public int ID = -1; /** * The font name */ public String name; public Font() { } } /** * Helper class to hold the karaoke information. * The member variables in this class are read-only. */ public class Karaoke { /** * The start time (in milliseconds) to highlight the characters * specified by startChar and endChar. */ public int startTimeMs = -1; /** * The end time (in milliseconds) to highlight the characters * specified by startChar and endChar. */ public int endTimeMs = -1; /** * The offset of the start character to be highlighted */ public int startChar = -1; /** * The offset of the end character to be highlighted */ public int endChar = -1; public Karaoke() { } } /** * Helper class to hold the hyper text information. * The member variables in this class are read-only. */ public class HyperText { /** * The offset of the start character */ public int startChar = -1; /** * The offset of the end character */ public int endChar = -1; /** * The linked-to URL */ public String URL; /** * The "alt" string for user display */ public String altString; public HyperText() { } } /** * @param obj the byte array which contains the timed text. * @throws IllegalArgumentExcept if parseParcel() fails. * {@hide} */ public TimedText(byte[] obj) { mParcel.unmarshall(obj, 0, obj.length); if (!parseParcel()) { mKeyObjectMap.clear(); throw new IllegalArgumentException("parseParcel() fails"); } } /** * Go over all the records, collecting metadata keys and fields in the * Parcel. These are stored in mKeyObjectMap for application to retrieve. * @return false if an error occurred during parsing. Otherwise, true. */ private boolean parseParcel() { mParcel.setDataPosition(0); if (mParcel.dataAvail() == 0) { return false; } int type = mParcel.readInt(); if (type == KEY_LOCAL_SETTING) { type = mParcel.readInt(); if (type != KEY_START_TIME) { return false; } int mStartTimeMs = mParcel.readInt(); mKeyObjectMap.put(type, mStartTimeMs); type = mParcel.readInt(); if (type != KEY_STRUCT_TEXT) { return false; } mTextStruct = new Text(); mTextStruct.textLen = mParcel.readInt(); mTextStruct.text = mParcel.createByteArray(); mKeyObjectMap.put(type, mTextStruct); } else if (type != KEY_GLOBAL_SETTING) { Log.w(TAG, "Invalid timed text key found: " + type); return false; } while (mParcel.dataAvail() > 0) { int key = mParcel.readInt(); if (!isValidKey(key)) { Log.w(TAG, "Invalid timed text key found: " + key); return false; } Object object = null; switch (key) { case KEY_STRUCT_STYLE_LIST: { readStyle(); object = mStyleList; break; } case KEY_STRUCT_FONT_LIST: { readFont(); object = mFontList; break; } case KEY_STRUCT_HIGHLIGHT_LIST: { readHighlight(); object = mHighlightPosList; break; } case KEY_STRUCT_KARAOKE_LIST: { readKaraoke(); object = mKaraokeList; break; } case KEY_STRUCT_HYPER_TEXT_LIST: { readHyperText(); object = mHyperTextList; break; } case KEY_STRUCT_BLINKING_TEXT_LIST: { readBlinkingText(); object = mBlinkingPosList; break; } case KEY_WRAP_TEXT: { mWrapText = mParcel.readInt(); object = mWrapText; break; } case KEY_HIGHLIGHT_COLOR_RGBA: { mHighlightColorRGBA = mParcel.readInt(); object = mHighlightColorRGBA; break; } case KEY_DISPLAY_FLAGS: { mDisplayFlags = mParcel.readInt(); object = mDisplayFlags; break; } case KEY_STRUCT_JUSTIFICATION: { mJustification = new Justification(); mJustification.horizontalJustification = mParcel.readInt(); mJustification.verticalJustification = mParcel.readInt(); object = mJustification; break; } case KEY_BACKGROUND_COLOR_RGBA: { mBackgroundColorRGBA = mParcel.readInt(); object = mBackgroundColorRGBA; break; } case KEY_STRUCT_TEXT_POS: { mTextPos = new TextPos(); mTextPos.top = mParcel.readInt(); mTextPos.left = mParcel.readInt(); mTextPos.bottom = mParcel.readInt(); mTextPos.right = mParcel.readInt(); object = mTextPos; break; } case KEY_SCROLL_DELAY: { mScrollDelay = mParcel.readInt(); object = mScrollDelay; break; } default: { break; } } if (object != null) { if (mKeyObjectMap.containsKey(key)) { mKeyObjectMap.remove(key); } mKeyObjectMap.put(key, object); } } mParcel.recycle(); return true; } /** * To parse and store the Style list. */ private void readStyle() { Style style = new Style(); boolean endOfStyle = false; while (!endOfStyle && (mParcel.dataAvail() > 0)) { int key = mParcel.readInt(); switch (key) { case KEY_START_CHAR: { style.startChar = mParcel.readInt(); break; } case KEY_END_CHAR: { style.endChar = mParcel.readInt(); break; } case KEY_FONT_ID: { style.fontID = mParcel.readInt(); break; } case KEY_STYLE_FLAGS: { int flags = mParcel.readInt(); // In the absence of any bits set in flags, the text // is plain. Otherwise, 1: bold, 2: italic, 4: underline style.isBold = ((flags % 2) == 1); style.isItalic = ((flags % 4) >= 2); style.isUnderlined = ((flags / 4) == 1); break; } case KEY_FONT_SIZE: { style.fontSize = mParcel.readInt(); break; } case KEY_TEXT_COLOR_RGBA: { style.colorRGBA = mParcel.readInt(); break; } default: { // End of the Style parsing. Reset the data position back // to the position before the last mParcel.readInt() call. mParcel.setDataPosition(mParcel.dataPosition() - 4); endOfStyle = true; break; } } } if (mStyleList == null) { mStyleList = new ArrayList<Style>(); } mStyleList.add(style); } /** * To parse and store the Font list */ private void readFont() { int entryCount = mParcel.readInt(); for (int i = 0; i < entryCount; i++) { Font font = new Font(); font.ID = mParcel.readInt(); int nameLen = mParcel.readInt(); byte[] text = mParcel.createByteArray(); font.name = new String(text, 0, nameLen); if (mFontList == null) { mFontList = new ArrayList<Font>(); } mFontList.add(font); } } /** * To parse and store the Highlight list */ private void readHighlight() { CharPos pos = new CharPos(); pos.startChar = mParcel.readInt(); pos.endChar = mParcel.readInt(); if (mHighlightPosList == null) { mHighlightPosList = new ArrayList<CharPos>(); } mHighlightPosList.add(pos); } /** * To parse and store the Karaoke list */ private void readKaraoke() { int entryCount = mParcel.readInt(); for (int i = 0; i < entryCount; i++) { Karaoke kara = new Karaoke(); kara.startTimeMs = mParcel.readInt(); kara.endTimeMs = mParcel.readInt(); kara.startChar = mParcel.readInt(); kara.endChar = mParcel.readInt(); if (mKaraokeList == null) { mKaraokeList = new ArrayList<Karaoke>(); } mKaraokeList.add(kara); } } /** * To parse and store HyperText list */ private void readHyperText() { HyperText hyperText = new HyperText(); hyperText.startChar = mParcel.readInt(); hyperText.endChar = mParcel.readInt(); int len = mParcel.readInt(); byte[] url = mParcel.createByteArray(); hyperText.URL = new String(url, 0, len); len = mParcel.readInt(); byte[] alt = mParcel.createByteArray(); hyperText.altString = new String(alt, 0, len); if (mHyperTextList == null) { mHyperTextList = new ArrayList<HyperText>(); } mHyperTextList.add(hyperText); } /** * To parse and store blinking text list */ private void readBlinkingText() { CharPos blinkingPos = new CharPos(); blinkingPos.startChar = mParcel.readInt(); blinkingPos.endChar = mParcel.readInt(); if (mBlinkingPosList == null) { mBlinkingPosList = new ArrayList<CharPos>(); } mBlinkingPosList.add(blinkingPos); } /** * To check whether the given key is valid. * @param key the key to be checked. * @return true if the key is a valid one. Otherwise, false. */ public boolean isValidKey(final int key) { if (!((key >= FIRST_PUBLIC_KEY) && (key <= LAST_PUBLIC_KEY)) && !((key >= FIRST_PRIVATE_KEY) && (key <= LAST_PRIVATE_KEY))) { return false; } return true; } /** * To check whether the given key is contained in this TimedText object. * @param key the key to be checked. * @return true if the key is contained in this TimedText object. * Otherwise, false. */ public boolean containsKey(final int key) { if (isValidKey(key) && mKeyObjectMap.containsKey(key)) { return true; } return false; } /** * @return a set of the keys contained in this TimedText object. */ public Set keySet() { return mKeyObjectMap.keySet(); } /** * To retrieve the object associated with the key. Caller must make sure * the key is present using the containsKey method otherwise a * RuntimeException will occur. * @param key the key used to retrieve the object. * @return an object. The object could be an instanceof Integer, List, or * any of the helper classes such as TextPos, Justification, and Text. */ public Object getObject(final int key) { if (containsKey(key)) { return mKeyObjectMap.get(key); } else { throw new IllegalArgumentException("Invalid key: " + key); } } }