content: /*
   * 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.annotation.UnsupportedAppUsage;
  import android.graphics.Rect;
  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, including:
   * <ul>
   * <li> The characters for rendering</li>
   * <li> The rendering position for the timed text</li>
   * </ul>
   *
   * <p> To render the timed text, applications need to do the following:
   *
   * <ul>
   * <li> Implement the {@link MediaPlayer.OnTimedTextListener} interface</li>
   * <li> Register the {@link MediaPlayer.OnTimedTextListener} callback on a MediaPlayer object that is used for playback</li>
   * <li> When a onTimedText callback is received, do the following:
   * <ul>
   * <li> call {@link #getText} to get the characters for rendering</li>
   * <li> call {@link #getBounds} to get the text rendering area/region</li>
   * </ul>
   * </li>
   * </ul>
   *
   * @see android.media.MediaPlayer
   */
  public final class TimedText
  {
      private static final int FIRST_PUBLIC_KEY                 = 1;
  
      // These keys must be in sync with the keys in TextDescription.h
      private static final int KEY_DISPLAY_FLAGS                 = 1; // int
      private static final int KEY_STYLE_FLAGS                   = 2; // int
      private static final int KEY_BACKGROUND_COLOR_RGBA         = 3; // int
      private static final int KEY_HIGHLIGHT_COLOR_RGBA          = 4; // int
      private static final int KEY_SCROLL_DELAY                  = 5; // int
      private static final int KEY_WRAP_TEXT                     = 6; // int
      private static final int KEY_START_TIME                    = 7; // int
      private static final int KEY_STRUCT_BLINKING_TEXT_LIST     = 8; // List<CharPos>
      private static final int KEY_STRUCT_FONT_LIST              = 9; // List<Font>
      private static final int KEY_STRUCT_HIGHLIGHT_LIST         = 10; // List<CharPos>
      private static final int KEY_STRUCT_HYPER_TEXT_LIST        = 11; // List<HyperText>
      private static final int KEY_STRUCT_KARAOKE_LIST           = 12; // List<Karaoke>
      private static final int KEY_STRUCT_STYLE_LIST             = 13; // List<Style>
      private static final int KEY_STRUCT_TEXT_POS               = 14; // TextPos
      private static final int KEY_STRUCT_JUSTIFICATION          = 15; // Justification
      private 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 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 Rect mTextBounds = null;
      private String mTextChars = null;
  
      private Justification mJustification;
  
      /**
       * 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.
       * {@hide}
       */
      public static final class CharPos {
          /**
           * The offset of the start character
           */
          public final int startChar;
  
          /**
           * The offset of the end character
           */
          public final int endChar;
  
          /**
           * Constuctor
           * @param startChar the offset of the start character.
           * @param endChar the offset of the end character.
           */
          public CharPos(int startChar, int endChar) {
              this.startChar = startChar;
              this.endChar = endChar;
          }
      }
  
      /**
       * Helper class to hold the justification for text display in the text box.
       * The member variables in this class are read-only.
       * {@hide}
       */
      public static final class Justification {
          /**
           * horizontal justification  0: left, 1: centered, -1: right
           */
          public final int horizontalJustification;
  
          /**
           * vertical justification  0: top, 1: centered, -1: bottom
           */
          public final int verticalJustification;
  
          /**
           * Constructor
           * @param horizontal the horizontal justification of the text.
           * @param vertical the vertical justification of the text.
           */
          public Justification(int horizontal, int vertical) {
              this.horizontalJustification = horizontal;
              this.verticalJustification = vertical;
          }
      }
  
      /**
       * Helper class to hold the style information to display the text.
       * The member variables in this class are read-only.
       * {@hide}
       */
      public static final class Style {
          /**
           * The offset of the start character which applys this style
           */
          public final int startChar;
  
          /**
           * The offset of the end character which applys this style
           */
          public final int endChar;
  
          /**
           * ID of the font. This ID will be used to choose the font
           * to be used from the font list.
           */
          public final int fontID;
  
          /**
           * True if the characters should be bold
           */
          public final boolean isBold;
  
          /**
           * True if the characters should be italic
           */
          public final boolean isItalic;
  
          /**
           * True if the characters should be underlined
           */
          public final boolean isUnderlined;
  
          /**
           * The size of the font
           */
          public final int fontSize;
  
          /**
           * To specify the RGBA color: 8 bits each of red, green, blue,
           * and an alpha(transparency) value
           */
          public final int colorRGBA;
  
          /**
           * Constructor
           * @param startChar the offset of the start character which applys this style
           * @param endChar the offset of the end character which applys this style
           * @param fontId the ID of the font.
           * @param isBold whether the characters should be bold.
           * @param isItalic whether the characters should be italic.
           * @param isUnderlined whether the characters should be underlined.
           * @param fontSize the size of the font.
           * @param colorRGBA red, green, blue, and alpha value for color.
           */
          public Style(int startChar, int endChar, int fontId,
                       boolean isBold, boolean isItalic, boolean isUnderlined,
                       int fontSize, int colorRGBA) {
              this.startChar = startChar;
              this.endChar = endChar;
              this.fontID = fontId;
              this.isBold = isBold;
              this.isItalic = isItalic;
              this.isUnderlined = isUnderlined;
              this.fontSize = fontSize;
              this.colorRGBA = colorRGBA;
          }
      }
  
      /**
       * Helper class to hold the font ID and name.
       * The member variables in this class are read-only.
       * {@hide}
       */
      public static final class Font {
          /**
           * The font ID
           */
          public final int ID;
  
          /**
           * The font name
           */
          public final String name;
  
          /**
           * Constructor
           * @param id the font ID.
           * @param name the font name.
           */
          public Font(int id, String name) {
              this.ID = id;
              this.name = name;
          }
      }
  
      /**
       * Helper class to hold the karaoke information.
       * The member variables in this class are read-only.
       * {@hide}
       */
      public static final class Karaoke {
          /**
           * The start time (in milliseconds) to highlight the characters
           * specified by startChar and endChar.
           */
          public final int startTimeMs;
  
          /**
           * The end time (in milliseconds) to highlight the characters
           * specified by startChar and endChar.
           */
          public final int endTimeMs;
  
          /**
           * The offset of the start character to be highlighted
           */
          public final int startChar;
  
          /**
           * The offset of the end character to be highlighted
           */
          public final int endChar;
  
          /**
           * Constructor
           * @param startTimeMs the start time (in milliseconds) to highlight
           * the characters between startChar and endChar.
           * @param endTimeMs the end time (in milliseconds) to highlight
           * the characters between startChar and endChar.
           * @param startChar the offset of the start character to be highlighted.
           * @param endChar the offset of the end character to be highlighted.
           */
          public Karaoke(int startTimeMs, int endTimeMs, int startChar, int endChar) {
              this.startTimeMs = startTimeMs;
              this.endTimeMs = endTimeMs;
              this.startChar = startChar;
              this.endChar = endChar;
          }
      }
  
      /**
       * Helper class to hold the hyper text information.
       * The member variables in this class are read-only.
       * {@hide}
       */
      public static final class HyperText {
          /**
           * The offset of the start character
           */
          public final int startChar;
  
          /**
           * The offset of the end character
           */
          public final int endChar;
  
          /**
           * The linked-to URL
           */
          public final String URL;
  
          /**
           * The "alt" string for user display
           */
          public final String altString;
  
  
          /**
           * Constructor
           * @param startChar the offset of the start character.
           * @param endChar the offset of the end character.
           * @param url the linked-to URL.
           * @param alt the "alt" string for display.
           */
          public HyperText(int startChar, int endChar, String url, String alt) {
              this.startChar = startChar;
              this.endChar = endChar;
              this.URL = url;
              this.altString = alt;
          }
      }
  
      /**
       * @param obj the byte array which contains the timed text.
       * @throws IllegalArgumentExcept if parseParcel() fails.
       * {@hide}
       */
      public TimedText(Parcel parcel) {
          if (!parseParcel(parcel)) {
              mKeyObjectMap.clear();
              throw new IllegalArgumentException("parseParcel() fails");
          }
      }
  
      /**
       * @param text the characters in the timed text.
       * @param bounds the rectangle area or region for rendering the timed text.
       * {@hide}
       */
      public TimedText(String text, Rect bounds) {
          mTextChars = text;
          mTextBounds = bounds;
      }
  
      /**
       * Get the characters in the timed text.
       *
       * @return the characters as a String object in the TimedText. Applications
       * should stop rendering previous timed text at the current rendering region if
       * a null is returned, until the next non-null timed text is received.
       */
      public String getText() {
          return mTextChars;
      }
  
      /**
       * Get the rectangle area or region for rendering the timed text as specified
       * by a Rect object.
       *
       * @return the rectangle region to render the characters in the timed text.
       * If no bounds information is available (a null is returned), render the
       * timed text at the center bottom of the display.
       */
      public Rect getBounds() {
          return mTextBounds;
      }
  
      /*
       * 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(Parcel parcel) {
          parcel.setDataPosition(0);
          if (parcel.dataAvail() == 0) {
              return false;
          }
  
          int type = parcel.readInt();
          if (type == KEY_LOCAL_SETTING) {
              type = parcel.readInt();
              if (type != KEY_START_TIME) {
                  return false;
              }
              int mStartTimeMs = parcel.readInt();
              mKeyObjectMap.put(type, mStartTimeMs);
  
              type = parcel.readInt();
              if (type != KEY_STRUCT_TEXT) {
                  return false;
              }
  
              int textLen = parcel.readInt();
              byte[] text = parcel.createByteArray();
              if (text == null || text.length == 0) {
                  mTextChars = null;
              } else {
                  mTextChars = new String(text);
              }
  
          } else if (type != KEY_GLOBAL_SETTING) {
              Log.w(TAG, "Invalid timed text key found: " + type);
              return false;
          }
  
          while (parcel.dataAvail() > 0) {
              int key = parcel.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(parcel);
                      object = mStyleList;
                      break;
                  }
                  case KEY_STRUCT_FONT_LIST: {
                      readFont(parcel);
                      object = mFontList;
                      break;
                  }
                  case KEY_STRUCT_HIGHLIGHT_LIST: {
                      readHighlight(parcel);
                      object = mHighlightPosList;
                      break;
                  }
                  case KEY_STRUCT_KARAOKE_LIST: {
                      readKaraoke(parcel);
                      object = mKaraokeList;
                      break;
                  }
                  case KEY_STRUCT_HYPER_TEXT_LIST: {
                      readHyperText(parcel);
                      object = mHyperTextList;
  
                      break;
                  }
                  case KEY_STRUCT_BLINKING_TEXT_LIST: {
                      readBlinkingText(parcel);
                      object = mBlinkingPosList;
  
                      break;
                  }
                  case KEY_WRAP_TEXT: {
                      mWrapText = parcel.readInt();
                      object = mWrapText;
                      break;
                  }
                  case KEY_HIGHLIGHT_COLOR_RGBA: {
                      mHighlightColorRGBA = parcel.readInt();
                      object = mHighlightColorRGBA;
                      break;
                  }
                  case KEY_DISPLAY_FLAGS: {
                      mDisplayFlags = parcel.readInt();
                      object = mDisplayFlags;
                      break;
                  }
                  case KEY_STRUCT_JUSTIFICATION: {
  
                      int horizontal = parcel.readInt();
                      int vertical = parcel.readInt();
                      mJustification = new Justification(horizontal, vertical);
  
                      object = mJustification;
                      break;
                  }
                  case KEY_BACKGROUND_COLOR_RGBA: {
                      mBackgroundColorRGBA = parcel.readInt();
                      object = mBackgroundColorRGBA;
                      break;
                  }
                  case KEY_STRUCT_TEXT_POS: {
                      int top = parcel.readInt();
                      int left = parcel.readInt();
                      int bottom = parcel.readInt();
                      int right = parcel.readInt();
                      mTextBounds = new Rect(left, top, right, bottom);
  
                      break;
                  }
                  case KEY_SCROLL_DELAY: {
                      mScrollDelay = parcel.readInt();
                      object = mScrollDelay;
                      break;
                  }
                  default: {
                      break;
                  }
              }
  
              if (object != null) {
                  if (mKeyObjectMap.containsKey(key)) {
                      mKeyObjectMap.remove(key);
                  }
                  // Previous mapping will be replaced with the new object, if there was one.
                  mKeyObjectMap.put(key, object);
              }
          }
  
          return true;
      }
  
      /*
       * To parse and store the Style list.
       */
      private void readStyle(Parcel parcel) {
          boolean endOfStyle = false;
          int startChar = -1;
          int endChar = -1;
          int fontId = -1;
          boolean isBold = false;
          boolean isItalic = false;
          boolean isUnderlined = false;
          int fontSize = -1;
          int colorRGBA = -1;
          while (!endOfStyle && (parcel.dataAvail() > 0)) {
              int key = parcel.readInt();
              switch (key) {
                  case KEY_START_CHAR: {
                      startChar = parcel.readInt();
                      break;
                  }
                  case KEY_END_CHAR: {
                      endChar = parcel.readInt();
                      break;
                  }
                  case KEY_FONT_ID: {
                      fontId = parcel.readInt();
                      break;
                  }
                  case KEY_STYLE_FLAGS: {
                      int flags = parcel.readInt();
                      // In the absence of any bits set in flags, the text
                      // is plain. Otherwise, 1: bold, 2: italic, 4: underline
                      isBold = ((flags % 2) == 1);
                      isItalic = ((flags % 4) >= 2);
                      isUnderlined = ((flags / 4) == 1);
                      break;
                  }
                  case KEY_FONT_SIZE: {
                      fontSize = parcel.readInt();
                      break;
                  }
                  case KEY_TEXT_COLOR_RGBA: {
                      colorRGBA = parcel.readInt();
                      break;
                  }
                  default: {
                      // End of the Style parsing. Reset the data position back
                      // to the position before the last parcel.readInt() call.
                      parcel.setDataPosition(parcel.dataPosition() - 4);
                      endOfStyle = true;
                      break;
                  }
              }
          }
  
          Style style = new Style(startChar, endChar, fontId, isBold,
                                  isItalic, isUnderlined, fontSize, colorRGBA);
          if (mStyleList == null) {
              mStyleList = new ArrayList<Style>();
          }
          mStyleList.add(style);
      }
  
      /*
       * To parse and store the Font list
       */
      private void readFont(Parcel parcel) {
          int entryCount = parcel.readInt();
  
          for (int i = 0; i < entryCount; i++) {
              int id = parcel.readInt();
              int nameLen = parcel.readInt();
  
              byte[] text = parcel.createByteArray();
              final String name = new String(text, 0, nameLen);
  
              Font font = new Font(id, name);
  
              if (mFontList == null) {
                  mFontList = new ArrayList<Font>();
              }
              mFontList.add(font);
          }
      }
  
      /*
       * To parse and store the Highlight list
       */
      private void readHighlight(Parcel parcel) {
          int startChar = parcel.readInt();
          int endChar = parcel.readInt();
          CharPos pos = new CharPos(startChar, endChar);
  
          if (mHighlightPosList == null) {
              mHighlightPosList = new ArrayList<CharPos>();
          }
          mHighlightPosList.add(pos);
      }
  
      /*
       * To parse and store the Karaoke list
       */
      private void readKaraoke(Parcel parcel) {
          int entryCount = parcel.readInt();
  
          for (int i = 0; i < entryCount; i++) {
              int startTimeMs = parcel.readInt();
              int endTimeMs = parcel.readInt();
              int startChar = parcel.readInt();
              int endChar = parcel.readInt();
              Karaoke kara = new Karaoke(startTimeMs, endTimeMs,
                                         startChar, endChar);
  
              if (mKaraokeList == null) {
                  mKaraokeList = new ArrayList<Karaoke>();
              }
              mKaraokeList.add(kara);
          }
      }
  
      /*
       * To parse and store HyperText list
       */
      private void readHyperText(Parcel parcel) {
          int startChar = parcel.readInt();
          int endChar = parcel.readInt();
  
          int len = parcel.readInt();
          byte[] url = parcel.createByteArray();
          final String urlString = new String(url, 0, len);
  
          len = parcel.readInt();
          byte[] alt = parcel.createByteArray();
          final String altString = new String(alt, 0, len);
          HyperText hyperText = new HyperText(startChar, endChar, urlString, altString);
  
  
          if (mHyperTextList == null) {
              mHyperTextList = new ArrayList<HyperText>();
          }
          mHyperTextList.add(hyperText);
      }
  
      /*
       * To parse and store blinking text list
       */
      private void readBlinkingText(Parcel parcel) {
          int startChar = parcel.readInt();
          int endChar = parcel.readInt();
          CharPos blinkingPos = new CharPos(startChar, endChar);
  
          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.
       */
      private 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.
       */
      private 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.
       */
      private 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 1) an instance of Integer; 2) a
       * List of CharPos, Karaoke, Font, Style, and HyperText, or 3) an instance of
       * Justification.
       */
      @UnsupportedAppUsage
      private Object getObject(final int key) {
          if (containsKey(key)) {
              return mKeyObjectMap.get(key);
          } else {
              throw new IllegalArgumentException("Invalid key: " + key);
          }
      }
  }
  