diff options
22 files changed, 1533 insertions, 233 deletions
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index 2b9c082d5696..9bab797b4c54 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -82,11 +82,8 @@ interface IUsbManager /* Clears default preferences and permissions for the package */ void clearDefaults(String packageName); - /* Sets the current primary USB function. */ - void setPrimaryFunction(String functions); - - /* Sets the default primary USB function. */ - void setDefaultFunction(String functions); + /* Sets the current USB function. */ + void setCurrentFunction(String function, boolean makeDefault); /* Sets the file path for USB mass storage backing file. */ void setMassStorageBackingFile(String path); diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index a828a23b2f44..67d200cd40ff 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -408,32 +408,18 @@ public class UsbManager { } /** - * Sets the primary USB function. + * Sets the current USB function. * * @param function name of the USB function + * @param makeDefault true if this should be set as the default * * {@hide} */ - public void setPrimaryFunction(String function) { + public void setCurrentFunction(String function, boolean makeDefault) { try { - mService.setPrimaryFunction(function); + mService.setCurrentFunction(function, makeDefault); } catch (RemoteException e) { - Log.e(TAG, "RemoteException in setPrimaryFunction", e); - } - } - - /** - * Sets the default primary USB function. - * - * @param function name of the USB function - * - * {@hide} - */ - public void setDefaultFunction(String function) { - try { - mService.setDefaultFunction(function); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in setDefaultFunction", e); + Log.e(TAG, "RemoteException in setCurrentFunction", e); } } diff --git a/core/java/android/view/ViewAncestor.java b/core/java/android/view/ViewAncestor.java index afbedafa6eac..40e99a1788c2 100644 --- a/core/java/android/view/ViewAncestor.java +++ b/core/java/android/view/ViewAncestor.java @@ -1000,8 +1000,7 @@ public final class ViewAncestor extends Handler implements ViewParent, + "x" + desiredWindowHeight + "..."); boolean goodMeasure = false; - if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT - || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { + if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) { // On large screens, we don't want to allow dialogs to just // stretch to fill the entire width of the screen to display // one line of text. First try doing the layout at a smaller diff --git a/core/java/android/webkit/JniUtil.java b/core/java/android/webkit/JniUtil.java index b5d493368844..bb4d192516b9 100644 --- a/core/java/android/webkit/JniUtil.java +++ b/core/java/android/webkit/JniUtil.java @@ -23,6 +23,10 @@ import android.util.Log; import java.io.InputStream; class JniUtil { + + static { + System.loadLibrary("webcore"); + } private static final String LOGTAG = "webkit"; private JniUtil() {} // Utility class, do not instantiate. diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp index e301e44c04bf..22d3c6f44494 100644 --- a/core/jni/android_view_GLES20Canvas.cpp +++ b/core/jni/android_view_GLES20Canvas.cpp @@ -649,7 +649,8 @@ static void android_view_GLES20Canvas_updateTextureLayer(JNIEnv* env, jobject cl float transform[16]; sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, surface)); - surfaceTexture->updateTexImage(); + while (surfaceTexture->getQueuedCount() > 0) + surfaceTexture->updateTexImage(); surfaceTexture->getTransformMatrix(transform); GLenum renderTarget = surfaceTexture->getCurrentTextureTarget(); diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h index deade5ec5238..99b72ad86415 100644 --- a/include/media/stagefright/MetaData.h +++ b/include/media/stagefright/MetaData.h @@ -118,6 +118,9 @@ enum { // The language code for this media kKeyMediaLanguage = 'lang', // cstring + + // To store the timed text format data + kKeyTextFormatData = 'text', // raw data }; enum { diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 255773067919..33312d195656 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -1568,7 +1568,14 @@ public class MediaPlayer return; case MEDIA_TIMED_TEXT: if (mOnTimedTextListener != null) { - mOnTimedTextListener.onTimedText(mMediaPlayer, (String)msg.obj); + if (msg.obj == null) { + mOnTimedTextListener.onTimedText(mMediaPlayer, null); + } else { + if (msg.obj instanceof byte[]) { + TimedText text = new TimedText((byte[])(msg.obj)); + mOnTimedTextListener.onTimedText(mMediaPlayer, text); + } + } } return; @@ -1755,14 +1762,14 @@ public class MediaPlayer public interface OnTimedTextListener { /** - * Called to indicate the video size + * Called to indicate an avaliable timed text * * @param mp the MediaPlayer associated with this callback - * @param text the timed text sample which contains the - * text needed to be displayed. + * @param text the timed text sample which contains the text + * needed to be displayed and the display format. * {@hide} */ - public void onTimedText(MediaPlayer mp, String text); + public void onTimedText(MediaPlayer mp, TimedText text); } /** diff --git a/media/java/android/media/TimedText.java b/media/java/android/media/TimedText.java new file mode 100644 index 000000000000..a055c8bac874 --- /dev/null +++ b/media/java/android/media/TimedText.java @@ -0,0 +1,655 @@ +/* + * 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); + } + } +} diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp index 5fe511fbb543..5582f9278a64 100644 --- a/media/libstagefright/MPEG4Extractor.cpp +++ b/media/libstagefright/MPEG4Extractor.cpp @@ -889,11 +889,17 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) { uint32_t entry_count = U32_AT(&buffer[4]); if (entry_count > 1) { - // For now we only support a single type of media per track. - - mLastTrack->skipTrack = true; - *offset += chunk_size; - break; + // For 3GPP timed text, there could be multiple tx3g boxes contain + // multiple text display formats. These formats will be used to + // display the timed text. + const char *mime; + CHECK(mLastTrack->meta->findCString(kKeyMIMEType, &mime)); + if (strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) { + // For now we only support a single type of media per track. + mLastTrack->skipTrack = true; + *offset += chunk_size; + break; + } } off64_t stop_offset = *offset + chunk_size; @@ -1324,9 +1330,53 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) { return parseDrmSINF(offset, data_offset); } + case FOURCC('h', 'd', 'l', 'r'): + { + uint32_t buffer; + if (mDataSource->readAt( + data_offset + 8, &buffer, 4) < 4) { + return ERROR_IO; + } + + uint32_t type = ntohl(buffer); + // For the 3GPP file format, the handler-type within the 'hdlr' box + // shall be 'text' + if (type == FOURCC('t', 'e', 'x', 't')) { + mLastTrack->meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_TEXT_3GPP); + } + + *offset += chunk_size; + break; + } + case FOURCC('t', 'x', '3', 'g'): { - mLastTrack->meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_TEXT_3GPP); + uint32_t type; + const void *data; + size_t size = 0; + if (!mLastTrack->meta->findData( + kKeyTextFormatData, &type, &data, &size)) { + size = 0; + } + + uint8_t *buffer = new uint8_t[size + chunk_size]; + + if (size > 0) { + memcpy(buffer, data, size); + } + + if ((size_t)(mDataSource->readAt(*offset, buffer + size, chunk_size)) + < chunk_size) { + delete[] buffer; + buffer = NULL; + + return ERROR_IO; + } + + mLastTrack->meta->setData( + kKeyTextFormatData, 0, buffer, size + chunk_size); + + delete[] buffer; *offset += chunk_size; break; diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp index ea9911cdee17..f075699d6a47 100644 --- a/media/libstagefright/MPEG4Writer.cpp +++ b/media/libstagefright/MPEG4Writer.cpp @@ -205,6 +205,8 @@ private: // Duration is time scale based void addOneSttsTableEntry(size_t sampleCount, int32_t timescaledDur); void addOneCttsTableEntry(size_t sampleCount, int32_t timescaledDur); + + bool isTrackMalFormed() const; void sendTrackSummary(bool hasMultipleTracks); // Write the boxes @@ -1975,7 +1977,6 @@ status_t MPEG4Writer::Track::threadEntry() { } CHECK(timestampUs >= 0); - LOGV("%s media time stamp: %lld and previous paused duration %lld", mIsAudio? "Audio": "Video", timestampUs, previousPausedDurationUs); if (timestampUs > mTrackDurationUs) { @@ -2082,11 +2083,10 @@ status_t MPEG4Writer::Track::threadEntry() { } - if (mSampleSizes.empty() || // no samples written - (!mIsAudio && mNumStssTableEntries == 0) || // no sync frames for video - (OK != checkCodecSpecificData())) { // no codec specific data + if (isTrackMalFormed()) { err = ERROR_MALFORMED; } + mOwner->trackProgressStatus(mTrackId, -1, err); // Last chunk @@ -2136,6 +2136,24 @@ status_t MPEG4Writer::Track::threadEntry() { return err; } +bool MPEG4Writer::Track::isTrackMalFormed() const { + if (mSampleSizes.empty()) { // no samples written + LOGE("The number of recorded samples is 0"); + return true; + } + + if (!mIsAudio && mNumStssTableEntries == 0) { // no sync frames for video + LOGE("There are no sync frames for video track"); + return true; + } + + if (OK != checkCodecSpecificData()) { // no codec specific data + return true; + } + + return false; +} + void MPEG4Writer::Track::sendTrackSummary(bool hasMultipleTracks) { // Send track summary only if test mode is enabled. diff --git a/media/libstagefright/timedtext/Android.mk b/media/libstagefright/timedtext/Android.mk index 9a6062cdd608..59d0e154c597 100644 --- a/media/libstagefright/timedtext/Android.mk +++ b/media/libstagefright/timedtext/Android.mk @@ -2,6 +2,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ + TextDescriptions.cpp \ TimedTextParser.cpp \ TimedTextPlayer.cpp diff --git a/media/libstagefright/timedtext/TextDescriptions.cpp b/media/libstagefright/timedtext/TextDescriptions.cpp new file mode 100644 index 000000000000..f9c1fe093803 --- /dev/null +++ b/media/libstagefright/timedtext/TextDescriptions.cpp @@ -0,0 +1,385 @@ +/* + * 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. + */ + +#include "TextDescriptions.h" +#include <media/stagefright/Utils.h> +#include <media/stagefright/MediaErrors.h> + +namespace android { + +TextDescriptions::TextDescriptions() { +} + +status_t TextDescriptions::getParcelOfDescriptions( + const uint8_t *data, ssize_t size, + uint32_t flags, int timeMs, Parcel *parcel) { + parcel->freeData(); + + if (flags & IN_BAND_TEXT_3GPP) { + if (flags & GLOBAL_DESCRIPTIONS) { + return extract3GPPGlobalDescriptions(data, size, parcel, 0); + } else if (flags & LOCAL_DESCRIPTIONS) { + return extract3GPPLocalDescriptions(data, size, timeMs, parcel, 0); + } + } else if (flags & OUT_OF_BAND_TEXT_SRT) { + if (flags & LOCAL_DESCRIPTIONS) { + return extractSRTLocalDescriptions(data, size, timeMs, parcel); + } + } + + return ERROR_UNSUPPORTED; +} + +// Parse the SRT text sample, and store the timing and text sample in a Parcel. +// The Parcel will be sent to MediaPlayer.java through event, and will be +// parsed in TimedText.java. +status_t TextDescriptions::extractSRTLocalDescriptions( + const uint8_t *data, ssize_t size, int timeMs, Parcel *parcel) { + parcel->writeInt32(KEY_LOCAL_SETTING); + parcel->writeInt32(KEY_START_TIME); + parcel->writeInt32(timeMs); + + parcel->writeInt32(KEY_STRUCT_TEXT); + // write the size of the text sample + parcel->writeInt32(size); + // write the text sample as a byte array + parcel->writeInt32(size); + parcel->write(data, size); + + return OK; +} + +// Extract the local 3GPP display descriptions. 3GPP local descriptions +// are appended to the text sample if any. The descriptions could include +// information such as text styles, highlights, karaoke and so on. They +// are contained in different boxes, such as 'styl' box contains text +// styles, and 'krok' box contains karaoke timing and positions. +status_t TextDescriptions::extract3GPPLocalDescriptions( + const uint8_t *data, ssize_t size, + int timeMs, Parcel *parcel, int depth) { + if (depth == 0) { + parcel->writeInt32(KEY_LOCAL_SETTING); + + // write start time to display this text sample + parcel->writeInt32(KEY_START_TIME); + parcel->writeInt32(timeMs); + + ssize_t textLen = (*data) << 8 | (*(data + 1)); + + // write text sample length and text sample itself + parcel->writeInt32(KEY_STRUCT_TEXT); + parcel->writeInt32(textLen); + parcel->writeInt32(textLen); + parcel->write(data + 2, textLen); + + if (size > textLen) { + data += (textLen + 2); + size -= (textLen + 2); + } else { + return OK; + } + } + + const uint8_t *tmpData = data; + ssize_t chunkSize = U32_AT(tmpData); + uint32_t chunkType = U32_AT(tmpData + 4); + + if (chunkSize <= 0) { + return OK; + } + + tmpData += 8; + + switch(chunkType) { + // 'styl' box specifies the style of the text. + case FOURCC('s', 't', 'y', 'l'): + { + uint16_t count = U16_AT(tmpData); + + tmpData += 2; + + for (int i = 0; i < count; i++) { + parcel->writeInt32(KEY_STRUCT_STYLE_LIST); + parcel->writeInt32(KEY_START_CHAR); + parcel->writeInt32(U16_AT(tmpData)); + + parcel->writeInt32(KEY_END_CHAR); + parcel->writeInt32(U16_AT(tmpData + 2)); + + parcel->writeInt32(KEY_FONT_ID); + parcel->writeInt32(U16_AT(tmpData + 4)); + + parcel->writeInt32(KEY_STYLE_FLAGS); + parcel->writeInt32(*(tmpData + 6)); + + parcel->writeInt32(KEY_FONT_SIZE); + parcel->writeInt32(*(tmpData + 7)); + + parcel->writeInt32(KEY_TEXT_COLOR_RGBA); + uint32_t rgba = *(tmpData + 8) << 24 | *(tmpData + 9) << 16 + | *(tmpData + 10) << 8 | *(tmpData + 11); + parcel->writeInt32(rgba); + + tmpData += 12; + } + + break; + } + // 'krok' box. The number of highlight events is specified, and each + // event is specified by a starting and ending char offset and an end + // time for the event. + case FOURCC('k', 'r', 'o', 'k'): + { + + parcel->writeInt32(KEY_STRUCT_KARAOKE_LIST); + + int startTime = U32_AT(tmpData); + uint16_t count = U16_AT(tmpData + 4); + parcel->writeInt32(count); + + tmpData += 6; + int lastEndTime = 0; + + for (int i = 0; i < count; i++) { + parcel->writeInt32(startTime + lastEndTime); + + lastEndTime = U32_AT(tmpData); + parcel->writeInt32(lastEndTime); + + parcel->writeInt32(U16_AT(tmpData + 4)); + parcel->writeInt32(U16_AT(tmpData + 6)); + + tmpData += 8; + } + + break; + } + // 'hlit' box specifies highlighted text + case FOURCC('h', 'l', 'i', 't'): + { + parcel->writeInt32(KEY_STRUCT_HIGHLIGHT_LIST); + + // the start char offset to highlight + parcel->writeInt32(U16_AT(tmpData)); + // the last char offset to highlight + parcel->writeInt32(U16_AT(tmpData + 2)); + + break; + } + // 'hclr' box specifies the RGBA color: 8 bits each of + // red, green, blue, and an alpha(transparency) value + case FOURCC('h', 'c', 'l', 'r'): + { + parcel->writeInt32(KEY_HIGHLIGHT_COLOR_RGBA); + + uint32_t rgba = *(tmpData) << 24 | *(tmpData + 1) << 16 + | *(tmpData + 2) << 8 | *(tmpData + 3); + parcel->writeInt32(rgba); + + break; + } + // 'dlay' box specifies a delay after a scroll in and/or + // before scroll out. + case FOURCC('d', 'l', 'a', 'y'): + { + parcel->writeInt32(KEY_SCROLL_DELAY); + + uint32_t delay = *(tmpData) << 24 | *(tmpData + 1) << 16 + | *(tmpData + 2) << 8 | *(tmpData + 3); + parcel->writeInt32(delay); + + break; + } + // 'href' box for hyper text link + case FOURCC('h', 'r', 'e', 'f'): + { + parcel->writeInt32(KEY_STRUCT_HYPER_TEXT_LIST); + + // the start offset of the text to be linked + parcel->writeInt32(U16_AT(tmpData)); + // the end offset of the text + parcel->writeInt32(U16_AT(tmpData + 2)); + + // the number of bytes in the following URL + int len = *(tmpData + 4); + parcel->writeInt32(len); + + // the linked-to URL + parcel->writeInt32(len); + parcel->write(tmpData + 5, len); + + tmpData += (5 + len); + + // the number of bytes in the following "alt" string + len = *tmpData; + parcel->writeInt32(len); + + // an "alt" string for user display + parcel->writeInt32(len); + parcel->write(tmpData + 1, len); + + break; + } + // 'tbox' box to indicate the position of the text with values + // of top, left, bottom and right + case FOURCC('t', 'b', 'o', 'x'): + { + parcel->writeInt32(KEY_STRUCT_TEXT_POS); + parcel->writeInt32(U16_AT(tmpData)); + parcel->writeInt32(U16_AT(tmpData + 2)); + parcel->writeInt32(U16_AT(tmpData + 4)); + parcel->writeInt32(U16_AT(tmpData + 6)); + + break; + } + // 'blnk' to specify the char range to be blinked + case FOURCC('b', 'l', 'n', 'k'): + { + parcel->writeInt32(KEY_STRUCT_BLINKING_TEXT_LIST); + + // start char offset + parcel->writeInt32(U16_AT(tmpData)); + // end char offset + parcel->writeInt32(U16_AT(tmpData + 2)); + + break; + } + // 'twrp' box specifies text wrap behavior. If the value if 0x00, + // then no wrap. If it's 0x01, then automatic 'soft' wrap is enabled. + // 0x02-0xff are reserved. + case FOURCC('t', 'w', 'r', 'p'): + { + parcel->writeInt32(KEY_WRAP_TEXT); + parcel->writeInt32(*tmpData); + + break; + } + default: + { + break; + } + } + + if (size > chunkSize) { + data += chunkSize; + size -= chunkSize; + // continue to parse next box + return extract3GPPLocalDescriptions(data, size, 0, parcel, 1); + } + + return OK; +} + +// To extract box 'tx3g' defined in 3GPP TS 26.245, and store it in a Parcel +status_t TextDescriptions::extract3GPPGlobalDescriptions( + const uint8_t *data, ssize_t size, Parcel *parcel, int depth) { + + ssize_t chunkSize = U32_AT(data); + uint32_t chunkType = U32_AT(data + 4); + const uint8_t *tmpData = data; + tmpData += 8; + + if (size < chunkSize) { + return OK; + } + + if (depth == 0) { + parcel->writeInt32(KEY_GLOBAL_SETTING); + } + switch(chunkType) { + case FOURCC('t', 'x', '3', 'g'): + { + tmpData += 8; // skip the first 8 bytes + parcel->writeInt32(KEY_DISPLAY_FLAGS); + parcel->writeInt32(U32_AT(tmpData)); + + parcel->writeInt32(KEY_STRUCT_JUSTIFICATION); + parcel->writeInt32(tmpData[4]); + parcel->writeInt32(tmpData[5]); + + parcel->writeInt32(KEY_BACKGROUND_COLOR_RGBA); + uint32_t rgba = *(tmpData + 6) << 24 | *(tmpData + 7) << 16 + | *(tmpData + 8) << 8 | *(tmpData + 9); + parcel->writeInt32(rgba); + + tmpData += 10; + parcel->writeInt32(KEY_STRUCT_TEXT_POS); + parcel->writeInt32(U16_AT(tmpData)); + parcel->writeInt32(U16_AT(tmpData + 2)); + parcel->writeInt32(U16_AT(tmpData + 4)); + parcel->writeInt32(U16_AT(tmpData + 6)); + + tmpData += 8; + parcel->writeInt32(KEY_STRUCT_STYLE_LIST); + parcel->writeInt32(KEY_START_CHAR); + parcel->writeInt32(U16_AT(tmpData)); + + parcel->writeInt32(KEY_END_CHAR); + parcel->writeInt32(U16_AT(tmpData + 2)); + + parcel->writeInt32(KEY_FONT_ID); + parcel->writeInt32(U16_AT(tmpData + 4)); + + parcel->writeInt32(KEY_STYLE_FLAGS); + parcel->writeInt32(*(tmpData + 6)); + + parcel->writeInt32(KEY_FONT_SIZE); + parcel->writeInt32(*(tmpData + 7)); + + parcel->writeInt32(KEY_TEXT_COLOR_RGBA); + rgba = *(tmpData + 8) << 24 | *(tmpData + 9) << 16 + | *(tmpData + 10) << 8 | *(tmpData + 11); + parcel->writeInt32(rgba); + + tmpData += 12; + parcel->writeInt32(KEY_STRUCT_FONT_LIST); + uint16_t count = U16_AT(tmpData); + parcel->writeInt32(count); + + tmpData += 2; + for (int i = 0; i < count; i++) { + // font ID + parcel->writeInt32(U16_AT(tmpData)); + + // font name length + parcel->writeInt32(*(tmpData + 2)); + + int len = *(tmpData + 2); + + parcel->write(tmpData + 3, len); + tmpData += 3 + len; + } + + break; + } + default: + { + break; + } + } + + data += chunkSize; + size -= chunkSize; + + if (size > 0) { + // continue to extract next 'tx3g' + return extract3GPPGlobalDescriptions(data, size, parcel, 1); + } + + return OK; +} + +} // namespace android diff --git a/media/libstagefright/timedtext/TextDescriptions.h b/media/libstagefright/timedtext/TextDescriptions.h new file mode 100644 index 000000000000..0144917505e9 --- /dev/null +++ b/media/libstagefright/timedtext/TextDescriptions.h @@ -0,0 +1,84 @@ + /* + * 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. + */ + +#ifndef TEXT_DESCRIPTIONS_H_ + +#define TEXT_DESCRIPTIONS_H_ + +#include <binder/Parcel.h> +#include <media/stagefright/foundation/ABase.h> + +namespace android { + +class TextDescriptions { +public: + enum { + IN_BAND_TEXT_3GPP = 0x01, + OUT_OF_BAND_TEXT_SRT = 0x02, + + GLOBAL_DESCRIPTIONS = 0x100, + LOCAL_DESCRIPTIONS = 0x200, + }; + + static status_t getParcelOfDescriptions( + const uint8_t *data, ssize_t size, + uint32_t flags, int timeMs, Parcel *parcel); +private: + TextDescriptions(); + + enum { + // These keys must be in sync with the keys in TimedText.java + KEY_DISPLAY_FLAGS = 1, // int + KEY_STYLE_FLAGS = 2, // int + KEY_BACKGROUND_COLOR_RGBA = 3, // int + KEY_HIGHLIGHT_COLOR_RGBA = 4, // int + KEY_SCROLL_DELAY = 5, // int + KEY_WRAP_TEXT = 6, // int + KEY_START_TIME = 7, // int + KEY_STRUCT_BLINKING_TEXT_LIST = 8, // List<CharPos> + KEY_STRUCT_FONT_LIST = 9, // List<Font> + KEY_STRUCT_HIGHLIGHT_LIST = 10, // List<CharPos> + KEY_STRUCT_HYPER_TEXT_LIST = 11, // List<HyperText> + KEY_STRUCT_KARAOKE_LIST = 12, // List<Karaoke> + KEY_STRUCT_STYLE_LIST = 13, // List<Style> + KEY_STRUCT_TEXT_POS = 14, // TextPos + KEY_STRUCT_JUSTIFICATION = 15, // Justification + KEY_STRUCT_TEXT = 16, // Text + + KEY_GLOBAL_SETTING = 101, + KEY_LOCAL_SETTING = 102, + KEY_START_CHAR = 103, + KEY_END_CHAR = 104, + KEY_FONT_ID = 105, + KEY_FONT_SIZE = 106, + KEY_TEXT_COLOR_RGBA = 107, + }; + + static status_t extractSRTLocalDescriptions( + const uint8_t *data, ssize_t size, + int timeMs, Parcel *parcel); + static status_t extract3GPPGlobalDescriptions( + const uint8_t *data, ssize_t size, + Parcel *parcel, int depth); + static status_t extract3GPPLocalDescriptions( + const uint8_t *data, ssize_t size, + int timeMs, Parcel *parcel, int depth); + + DISALLOW_EVIL_CONSTRUCTORS(TextDescriptions); +}; + +} // namespace android +#endif // TEXT_DESCRIPTIONS_H_ diff --git a/media/libstagefright/timedtext/TimedTextPlayer.cpp b/media/libstagefright/timedtext/TimedTextPlayer.cpp index 50bb16d3f2a9..7c8a747c518f 100644 --- a/media/libstagefright/timedtext/TimedTextPlayer.cpp +++ b/media/libstagefright/timedtext/TimedTextPlayer.cpp @@ -19,6 +19,7 @@ #include <utils/Log.h> #include <binder/IPCThreadState.h> + #include <media/stagefright/MediaDebug.h> #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MediaErrors.h> @@ -27,9 +28,11 @@ #include <media/stagefright/MediaBuffer.h> #include <media/stagefright/FileSource.h> #include <media/stagefright/Utils.h> + #include "include/AwesomePlayer.h" #include "TimedTextPlayer.h" #include "TimedTextParser.h" +#include "TextDescriptions.h" namespace android { @@ -92,10 +95,11 @@ status_t TimedTextPlayer::start(uint8_t index) { return BAD_VALUE; } + status_t err; if (index < mTextTrackVector.size()) { // start an in-band text mSource = mTextTrackVector.itemAt(index); - status_t err = mSource->start(); + err = mSource->start(); if (err != OK) { return err; @@ -112,13 +116,17 @@ status_t TimedTextPlayer::start(uint8_t index) { mTextParser = new TimedTextParser(); } - status_t err; if ((err = mTextParser->init(mOutOfBandSource, fileType)) != OK) { return err; } mTextType = kOutOfBandText; } + // send sample description format + if ((err = extractAndSendGlobalDescriptions()) != OK) { + return err; + } + int64_t positionUs; mObserver->getPosition(&positionUs); seekTo(positionUs); @@ -211,21 +219,17 @@ void TimedTextPlayer::onTextEvent() { } mTextEventPending = false; + if (mData.dataSize() > 0) { + notifyListener(MEDIA_TIMED_TEXT, &mData); + mData.freeData(); + } + MediaSource::ReadOptions options; if (mSeeking) { options.setSeekTo(mSeekTimeUs, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); mSeeking = false; - if (mTextType == kInBandText) { - if (mTextBuffer != NULL) { - mTextBuffer->release(); - mTextBuffer = NULL; - } - } else { - mText.clear(); - } - notifyListener(MEDIA_TIMED_TEXT); //empty text to clear the screen } @@ -233,32 +237,12 @@ void TimedTextPlayer::onTextEvent() { mObserver->getPosition(&positionUs); if (mTextType == kInBandText) { - if (mTextBuffer != NULL) { - uint8_t *tmp = (uint8_t *)(mTextBuffer->data()); - size_t len = (*tmp) << 8 | (*(tmp + 1)); - - notifyListener(MEDIA_TIMED_TEXT, - tmp + 2, - len); - - mTextBuffer->release(); - mTextBuffer = NULL; - - } - if (mSource->read(&mTextBuffer, &options) != OK) { return; } mTextBuffer->meta_data()->findInt64(kKeyTime, &timeUs); } else { - if (mText.size() > 0) { - notifyListener(MEDIA_TIMED_TEXT, - mText.c_str(), - mText.size()); - mText.clear(); - } - int64_t endTimeUs; if (mTextParser->getText( &mText, &timeUs, &endTimeUs, &options) != OK) { @@ -266,6 +250,19 @@ void TimedTextPlayer::onTextEvent() { } } + if (timeUs > 0) { + extractAndAppendLocalDescriptions(timeUs); + } + + if (mTextType == kInBandText) { + if (mTextBuffer != NULL) { + mTextBuffer->release(); + mTextBuffer = NULL; + } + } else { + mText.clear(); + } + //send the text now if (timeUs <= positionUs + 100000ll) { postTextEvent(); @@ -297,7 +294,8 @@ status_t TimedTextPlayer::setParameter(int key, const Parcel &request) { Mutex::Autolock autoLock(mLock); if (key == KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE) { - String8 uri = request.readString8(); + const String16 uri16 = request.readString16(); + String8 uri = String8(uri16); KeyedVector<String8, String8> headers; // To support local subtitle file only for now @@ -327,21 +325,92 @@ status_t TimedTextPlayer::setParameter(int key, const Parcel &request) { return INVALID_OPERATION; } -void TimedTextPlayer::notifyListener( - int msg, const void *data, size_t size) { +void TimedTextPlayer::notifyListener(int msg, const Parcel *parcel) { if (mListener != NULL) { sp<MediaPlayerBase> listener = mListener.promote(); if (listener != NULL) { - if (size > 0) { - mData.freeData(); - mData.write(data, size); - - listener->sendEvent(msg, 0, 0, &mData); + if (parcel && (parcel->dataSize() > 0)) { + listener->sendEvent(msg, 0, 0, parcel); } else { // send an empty timed text to clear the screen listener->sendEvent(msg); } } } } + +// Each text sample consists of a string of text, optionally with sample +// modifier description. The modifier description could specify a new +// text style for the string of text. These descriptions are present only +// if they are needed. This method is used to extract the modifier +// description and append it at the end of the text. +status_t TimedTextPlayer::extractAndAppendLocalDescriptions(int64_t timeUs) { + const void *data; + size_t size = 0; + int32_t flag = TextDescriptions::LOCAL_DESCRIPTIONS; + + if (mTextType == kInBandText) { + const char *mime; + CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime)); + + if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) { + flag |= TextDescriptions::IN_BAND_TEXT_3GPP; + data = mTextBuffer->data(); + size = mTextBuffer->size(); + } else { + // support 3GPP only for now + return ERROR_UNSUPPORTED; + } + } else { + data = mText.c_str(); + size = mText.size(); + flag |= TextDescriptions::OUT_OF_BAND_TEXT_SRT; + } + + if ((size > 0) && (flag != TextDescriptions::LOCAL_DESCRIPTIONS)) { + mData.freeData(); + return TextDescriptions::getParcelOfDescriptions( + (const uint8_t *)data, size, flag, timeUs / 1000, &mData); + } + + return OK; +} + +// To extract and send the global text descriptions for all the text samples +// in the text track or text file. +status_t TimedTextPlayer::extractAndSendGlobalDescriptions() { + const void *data; + size_t size = 0; + int32_t flag = TextDescriptions::GLOBAL_DESCRIPTIONS; + + if (mTextType == kInBandText) { + const char *mime; + CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime)); + + // support 3GPP only for now + if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) { + uint32_t type; + // get the 'tx3g' box content. This box contains the text descriptions + // used to render the text track + if (!mSource->getFormat()->findData( + kKeyTextFormatData, &type, &data, &size)) { + return ERROR_MALFORMED; + } + + flag |= TextDescriptions::IN_BAND_TEXT_3GPP; + } + } + + if ((size > 0) && (flag != TextDescriptions::GLOBAL_DESCRIPTIONS)) { + Parcel parcel; + if (TextDescriptions::getParcelOfDescriptions( + (const uint8_t *)data, size, flag, 0, &parcel) == OK) { + if (parcel.dataSize() > 0) { + notifyListener(MEDIA_TIMED_TEXT, &parcel); + } + } + } + + return OK; +} } diff --git a/media/libstagefright/timedtext/TimedTextPlayer.h b/media/libstagefright/timedtext/TimedTextPlayer.h index 590760b10977..a744db59417b 100644 --- a/media/libstagefright/timedtext/TimedTextPlayer.h +++ b/media/libstagefright/timedtext/TimedTextPlayer.h @@ -103,8 +103,10 @@ private: void postTextEvent(int64_t delayUs = -1); void cancelTextEvent(); - void notifyListener( - int msg, const void *data = NULL, size_t size = 0); + void notifyListener(int msg, const Parcel *parcel = NULL); + + status_t extractAndAppendLocalDescriptions(int64_t timeUs); + status_t extractAndSendGlobalDescriptions(); DISALLOW_EVIL_CONSTRUCTORS(TimedTextPlayer); }; diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPreferenceActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPreferenceActivity.java index 187a486f8ce2..60906a1e08dd 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbPreferenceActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPreferenceActivity.java @@ -1,93 +1,96 @@ -/*
- * 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 com.android.systemui.usb;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.hardware.usb.UsbManager;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.util.Log;
-import android.widget.Button;
-
-import java.io.File;
-
-import com.android.systemui.R;
-
-public class UsbPreferenceActivity extends Activity implements View.OnClickListener {
-
- private static final String TAG = "UsbPreferenceActivity";
-
- private UsbManager mUsbManager;
- private String mCurrentFunction;
- private String[] mFunctions;
- private String mInstallerImagePath;
- private Button mMtpPtpButton;
- private Button mInstallerCdButton;
- private boolean mPtpActive;
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
-
- AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
- dialogBuilder.setTitle(getString(R.string.usb_preference_title));
-
- LayoutInflater inflater = (LayoutInflater)getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- View buttonView = inflater.inflate(R.layout.usb_preference_buttons, null);
- dialogBuilder.setView(buttonView);
- mMtpPtpButton = (Button)buttonView.findViewById(R.id.mtp_ptp_button);
- mInstallerCdButton = (Button)buttonView.findViewById(R.id.installer_cd_button);
- mMtpPtpButton.setOnClickListener(this);
- mInstallerCdButton.setOnClickListener(this);
-
- mPtpActive = mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_PTP);
- if (mPtpActive) {
- mMtpPtpButton.setText(R.string.use_mtp_button_title);
- }
-
- mInstallerImagePath = getString(com.android.internal.R.string.config_isoImagePath);
- if (!(new File(mInstallerImagePath)).exists()) {
- mInstallerCdButton.setVisibility(View.GONE);
- }
-
- dialogBuilder.show();
- }
-
- public void onClick(View v) {
- if (v.equals(mMtpPtpButton)) {
- if (mPtpActive) {
- mUsbManager.setPrimaryFunction(UsbManager.USB_FUNCTION_MTP);
- mUsbManager.setDefaultFunction(UsbManager.USB_FUNCTION_MTP);
- } else {
- mUsbManager.setPrimaryFunction(UsbManager.USB_FUNCTION_PTP);
- mUsbManager.setDefaultFunction(UsbManager.USB_FUNCTION_PTP);
- }
- } else if (v.equals(mInstallerCdButton)) {
- mUsbManager.setPrimaryFunction(UsbManager.USB_FUNCTION_MASS_STORAGE);
- mUsbManager.setMassStorageBackingFile(mInstallerImagePath);
- }
-
+/* + * 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 com.android.systemui.usb; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.hardware.usb.UsbManager; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.util.Log; +import android.widget.Button; + +import java.io.File; + +import com.android.systemui.R; + +public class UsbPreferenceActivity extends Activity implements View.OnClickListener { + + private static final String TAG = "UsbPreferenceActivity"; + + private UsbManager mUsbManager; + private String mCurrentFunction; + private String[] mFunctions; + private String mInstallerImagePath; + private AlertDialog mDialog; + private Button mMtpPtpButton; + private Button mInstallerCdButton; + private boolean mPtpActive; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE); + + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + dialogBuilder.setTitle(getString(R.string.usb_preference_title)); + + LayoutInflater inflater = (LayoutInflater)getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + View buttonView = inflater.inflate(R.layout.usb_preference_buttons, null); + dialogBuilder.setView(buttonView); + mMtpPtpButton = (Button)buttonView.findViewById(R.id.mtp_ptp_button); + mInstallerCdButton = (Button)buttonView.findViewById(R.id.installer_cd_button); + mMtpPtpButton.setOnClickListener(this); + mInstallerCdButton.setOnClickListener(this); + + mPtpActive = mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_PTP); + if (mPtpActive) { + mMtpPtpButton.setText(R.string.use_mtp_button_title); + } + + mInstallerImagePath = getString(com.android.internal.R.string.config_isoImagePath); + if (!(new File(mInstallerImagePath)).exists()) { + mInstallerCdButton.setVisibility(View.GONE); + } + + mDialog = dialogBuilder.show(); + } + + public void onClick(View v) { + if (v.equals(mMtpPtpButton)) { + if (mPtpActive) { + mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MTP, true); + } else { + mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_PTP, true); + } + } else if (v.equals(mInstallerCdButton)) { + // installer CD is never default + mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MASS_STORAGE, false); + mUsbManager.setMassStorageBackingFile(mInstallerImagePath); + } + + if (mDialog != null) { + mDialog.dismiss(); + } finish();
- }
-}
+ } +} diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index 75f466a35fdd..baa4ec398354 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -498,6 +498,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { return; } + int width = WRAP_CONTENT; if (st.decorView == null || st.refreshDecorView) { if (st.decorView == null) { // Initialize the panel decor, this will populate st.decorView @@ -523,6 +524,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // If the contents is fill parent for the width, set the // corresponding background backgroundResId = st.fullBackground; + width = MATCH_PARENT; } else { // Otherwise, set the normal panel background backgroundResId = st.background; @@ -546,7 +548,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { st.isHandled = false; WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - WRAP_CONTENT, WRAP_CONTENT, + width, WRAP_CONTENT, st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG, WindowManager.LayoutParams.FLAG_DITHER | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index a9dfb22310d7..f6dd43a815b6 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -32,6 +32,7 @@ import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.ContentObserver; +import android.graphics.Rect; import android.net.Uri; import android.os.Binder; import android.os.Handler; @@ -814,6 +815,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final AtomicInteger mInteractionIdCounter = new AtomicInteger(); + final Rect mTempBounds = new Rect(); + // the events pending events to be dispatched to this service final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<AccessibilityEvent>(); @@ -932,9 +935,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilityNodeInfo info = mCallback.getFindAccessibilityNodeInfoResultAndClear( interactionId); if (info != null) { + applyCompatibilityScaleIfNeeded(info); info.setConnection(this); + info.setSealed(true); } - info.setSealed(true); return info; } catch (RemoteException re) { if (DEBUG) { @@ -979,6 +983,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final int infoCount = infos.size(); for (int i = 0; i < infoCount; i++) { AccessibilityNodeInfo info = infos.get(i); + applyCompatibilityScaleIfNeeded(info); info.setConnection(this); info.setSealed(true); } @@ -1019,6 +1024,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilityNodeInfo info = mCallback.getFindAccessibilityNodeInfoResultAndClear(interactionId); if (info != null) { + applyCompatibilityScaleIfNeeded(info); info.setConnection(this); info.setSealed(true); } @@ -1093,6 +1099,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } return mWindowIdToInteractionConnectionMap.get(windowId); } + + private void applyCompatibilityScaleIfNeeded(AccessibilityNodeInfo info) { + IBinder windowToken = mWindowIdToWindowTokenMap.get(info.getWindowId()); + final float scale = mWindowManagerService.getWindowCompatibilityScale(windowToken); + + if (scale == 1.0f) { + return; + } + + Rect bounds = mTempBounds; + info.getBoundsInParent(bounds); + bounds.scale(scale); + info.setBoundsInParent(bounds); + + info.getBoundsInScreen(bounds); + bounds.scale(scale); + info.setBoundsInScreen(bounds); + } } final class SecurityPolicy { diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index 15e67d0e2dc4..946a2700bed2 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -509,9 +509,9 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } try { if (enabled) { - usbManager.setPrimaryFunction(UsbManager.USB_FUNCTION_RNDIS); + usbManager.setCurrentFunction(UsbManager.USB_FUNCTION_RNDIS, false); } else { - usbManager.setPrimaryFunction(null); + usbManager.setCurrentFunction(null, false); } } catch (Exception e) { Log.e(TAG, "Error toggling usb RNDIS", e); diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java index b7f9d5ccb04a..918f1b6b8355 100644 --- a/services/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/java/com/android/server/usb/UsbDeviceManager.java @@ -77,9 +77,8 @@ public class UsbDeviceManager { private static final int MSG_UPDATE_STATE = 0; private static final int MSG_ENABLE_ADB = 1; - private static final int MSG_SET_PRIMARY_FUNCTION = 2; - private static final int MSG_SET_DEFAULT_FUNCTION = 3; - private static final int MSG_SYSTEM_READY = 4; + private static final int MSG_SET_CURRENT_FUNCTION = 2; + private static final int MSG_SYSTEM_READY = 3; // Delay for debouncing USB disconnects. // We often get rapid connect/disconnect events when enabling USB functions, @@ -227,7 +226,7 @@ public class UsbDeviceManager { mHandler.updateState(state); } else if ("START".equals(accessory)) { Slog.d(TAG, "got accessory start"); - setPrimaryFunction(UsbManager.USB_FUNCTION_ACCESSORY); + setCurrentFunction(UsbManager.USB_FUNCTION_ACCESSORY, false); } } }; @@ -371,6 +370,14 @@ public class UsbDeviceManager { sendMessage(m); } + public void sendMessage(int what, Object arg0, boolean arg1) { + removeMessages(what); + Message m = Message.obtain(this, what); + m.obj = arg0; + m.arg1 = (arg1 ? 1 : 0); + sendMessage(m); + } + public void updateState(String state) { int connected, configured; @@ -395,24 +402,30 @@ public class UsbDeviceManager { sendMessageDelayed(msg, (connected == 0) ? UPDATE_DELAY : 0); } - private boolean setUsbConfig(String config) { - // set the new configuration - SystemProperties.set("sys.usb.config", config); + private boolean waitForState(String state) { // wait for the transition to complete. // give up after 1 second. for (int i = 0; i < 20; i++) { // State transition is done when sys.usb.conf.done is set to the new configuration - if (config.equals(SystemProperties.get("sys.usb.state"))) return true; + if (state.equals(SystemProperties.get("sys.usb.state"))) return true; try { // try again in 50ms Thread.sleep(50); } catch (InterruptedException e) { } } + Log.e(TAG, "waitForState(" + state + ") FAILED"); return false; } - private void setCurrentFunctions(String functions) { + private boolean setUsbConfig(String config) { + Log.d(TAG, "setUsbConfig(" + config + ")"); + // set the new configuration + SystemProperties.set("sys.usb.config", config); + return waitForState(config); + } + + private void doSetCurrentFunctions(String functions) { if (!mCurrentFunctions.equals(functions)) { if (!setUsbConfig("none") || !setUsbConfig(functions)) { Log.e(TAG, "Failed to switch USB configuration to " + functions); @@ -428,17 +441,14 @@ public class UsbDeviceManager { if (enable != mAdbEnabled) { mAdbEnabled = enable; String functions; + // Due to the persist.sys.usb.config property trigger, changing adb state requires + // switching to default function if (enable) { - functions = addFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ADB); - mDefaultFunctions = addFunction(mDefaultFunctions, - UsbManager.USB_FUNCTION_ADB); + functions = addFunction(mDefaultFunctions, UsbManager.USB_FUNCTION_ADB); } else { - functions = removeFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ADB); - mDefaultFunctions = removeFunction(mDefaultFunctions, - UsbManager.USB_FUNCTION_ADB); + functions = removeFunction(mDefaultFunctions, UsbManager.USB_FUNCTION_ADB); } - SystemProperties.set("persist.sys.usb.config", mDefaultFunctions); - setCurrentFunctions(functions); + setCurrentFunction(functions, true); updateAdbNotification(mAdbEnabled && mConnected); } } @@ -449,7 +459,7 @@ public class UsbDeviceManager { } else { functionList = removeFunction(functionList, UsbManager.USB_FUNCTION_ADB); } - setCurrentFunctions(functionList); + doSetCurrentFunctions(functionList); } private void updateCurrentAccessory() { @@ -503,8 +513,6 @@ public class UsbDeviceManager { @Override public void handleMessage(Message msg) { - String function; - switch (msg.what) { case MSG_UPDATE_STATE: mConnected = (msg.arg1 == 1); @@ -518,7 +526,7 @@ public class UsbDeviceManager { if (!mConnected) { // restore defaults when USB is disconnected - setCurrentFunctions(mDefaultFunctions); + doSetCurrentFunctions(mDefaultFunctions); } if (mSystemReady) { updateUsbState(); @@ -527,20 +535,31 @@ public class UsbDeviceManager { case MSG_ENABLE_ADB: setAdbEnabled(msg.arg1 == 1); break; - case MSG_SET_PRIMARY_FUNCTION: - function = (String)msg.obj; - if (function == null) { - function = mDefaultFunctions; - } - setEnabledFunctions(function); - break; - case MSG_SET_DEFAULT_FUNCTION: - function = (String)msg.obj; - if (mAdbEnabled) { - function = addFunction(function, UsbManager.USB_FUNCTION_ADB); + case MSG_SET_CURRENT_FUNCTION: + String function = (String)msg.obj; + boolean makeDefault = (msg.arg1 == 1); + if (makeDefault) { + if (function == null) { + throw new NullPointerException(); + } + if (mAdbEnabled) { + function = addFunction(function, UsbManager.USB_FUNCTION_ADB); + } + + setUsbConfig("none"); + // setting this property will change the current USB state + // via a property trigger + SystemProperties.set("persist.sys.usb.config", function); + if (waitForState(function)) { + mCurrentFunctions = function; + mDefaultFunctions = function; + } + } else { + if (function == null) { + function = mDefaultFunctions; + } + setEnabledFunctions(function); } - SystemProperties.set("persist.sys.usb.config", function); - mDefaultFunctions = function; break; case MSG_SYSTEM_READY: updateUsbNotification(mConnected); @@ -588,15 +607,8 @@ public class UsbDeviceManager { return nativeOpenAccessory(); } - public void setPrimaryFunction(String function) { - mHandler.sendMessage(MSG_SET_PRIMARY_FUNCTION, function); - } - - public void setDefaultFunction(String function) { - if (function == null) { - throw new NullPointerException(); - } - mHandler.sendMessage(MSG_SET_DEFAULT_FUNCTION, function); + public void setCurrentFunction(String function, boolean makeDefault) { + mHandler.sendMessage(MSG_SET_CURRENT_FUNCTION, function, makeDefault); } public void setMassStorageBackingFile(String path) { diff --git a/services/java/com/android/server/usb/UsbService.java b/services/java/com/android/server/usb/UsbService.java index 193638f64186..9f2c17aa8ae5 100644 --- a/services/java/com/android/server/usb/UsbService.java +++ b/services/java/com/android/server/usb/UsbService.java @@ -146,19 +146,10 @@ public class UsbService extends IUsbManager.Stub { mSettingsManager.clearDefaults(packageName); } - public void setPrimaryFunction(String function) { + public void setCurrentFunction(String function, boolean makeDefault) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); if (mDeviceManager != null) { - mDeviceManager.setPrimaryFunction(function); - } else { - throw new IllegalStateException("USB device mode not supported"); - } - } - - public void setDefaultFunction(String function) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - if (mDeviceManager != null) { - mDeviceManager.setDefaultFunction(function); + mDeviceManager.setCurrentFunction(function, makeDefault); } else { throw new IllegalStateException("USB device mode not supported"); } diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 1c87f5b4002d..dba170ab7891 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -2776,6 +2776,13 @@ public class WindowManagerService extends IWindowManager.Stub Binder.restoreCallingIdentity(origId); } + public float getWindowCompatibilityScale(IBinder windowToken) { + synchronized (mWindowMap) { + WindowState windowState = mWindowMap.get(windowToken); + return (windowState != null) ? windowState.mGlobalScale : 1.0f; + } + } + private AttributeCache.Entry getCachedAnimations(WindowManager.LayoutParams lp) { if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: layout params pkg=" + (lp != null ? lp.packageName : null) |