Merge "resolved conflicts for merge of 77dd616e to master"
diff --git a/api/current.txt b/api/current.txt
index 6854965..94e4210 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -19381,17 +19381,22 @@
public class SuggestionSpan implements android.text.ParcelableSpan {
ctor public SuggestionSpan(android.content.Context, java.lang.String[], int);
ctor public SuggestionSpan(java.util.Locale, java.lang.String[], int);
- ctor public SuggestionSpan(android.content.Context, java.util.Locale, java.lang.String[], int, java.lang.String);
+ ctor public SuggestionSpan(android.content.Context, java.util.Locale, java.lang.String[], int, java.lang.Class<?>);
ctor public SuggestionSpan(android.os.Parcel);
method public int describeContents();
method public int getFlags();
method public java.lang.String getLocale();
- method public java.lang.String getOriginalString();
+ method public java.lang.Class<?> getNotificationTargetClass();
method public int getSpanTypeId();
method public java.lang.String[] getSuggestions();
method public void writeToParcel(android.os.Parcel, int);
+ field public static final java.lang.String ACTION_SUGGESTION_PICKED = "android.text.style.SUGGESTION_PICKED";
field public static final android.os.Parcelable.Creator CREATOR;
field public static final int FLAG_VERBATIM = 1; // 0x1
+ field public static final int SUGGESTIONS_MAX_SIZE = 5; // 0x5
+ field public static final java.lang.String SUGGESTION_SPAN_PICKED_AFTER = "after";
+ field public static final java.lang.String SUGGESTION_SPAN_PICKED_BEFORE = "before";
+ field public static final java.lang.String SUGGESTION_SPAN_PICKED_HASHCODE = "hashcode";
}
public class SuperscriptSpan extends android.text.style.MetricAffectingSpan implements android.text.ParcelableSpan {
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index dcb0898..effa5c8 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -19,8 +19,10 @@
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.SystemClock;
import android.text.ParcelableSpan;
import android.text.TextUtils;
+import android.util.Log;
import java.util.Arrays;
import java.util.Locale;
@@ -29,6 +31,7 @@
* Holds suggestion candidates of words under this span.
*/
public class SuggestionSpan implements ParcelableSpan {
+ private static final String TAG = SuggestionSpan.class.getSimpleName();
/**
* Flag for indicating that the input is verbatim. TextView refers to this flag to determine
@@ -36,7 +39,12 @@
*/
public static final int FLAG_VERBATIM = 0x0001;
- private static final int SUGGESTIONS_MAX_SIZE = 5;
+ public static final String ACTION_SUGGESTION_PICKED = "android.text.style.SUGGESTION_PICKED";
+ public static final String SUGGESTION_SPAN_PICKED_AFTER = "after";
+ public static final String SUGGESTION_SPAN_PICKED_BEFORE = "before";
+ public static final String SUGGESTION_SPAN_PICKED_HASHCODE = "hashcode";
+
+ public static final int SUGGESTIONS_MAX_SIZE = 5;
/*
* TODO: Needs to check the validity and add a feature that TextView will change
@@ -48,7 +56,9 @@
private final int mFlags;
private final String[] mSuggestions;
private final String mLocaleString;
- private final String mOriginalString;
+ private final Class<?> mNotificationTargetClass;
+ private final int mHashCode;
+
/*
* TODO: If switching IME is required, needs to add parameters for ids of InputMethodInfo
* and InputMethodSubtype.
@@ -77,10 +87,11 @@
* @param locale locale Locale of the suggestions
* @param suggestions Suggestions for the string under the span
* @param flags Additional flags indicating how this span is handled in TextView
- * @param originalString originalString for suggestions
+ * @param notificationTargetClass if not null, this class will get notified when the user
+ * selects one of the suggestions.
*/
public SuggestionSpan(Context context, Locale locale, String[] suggestions, int flags,
- String originalString) {
+ Class<?> notificationTargetClass) {
final int N = Math.min(SUGGESTIONS_MAX_SIZE, suggestions.length);
mSuggestions = Arrays.copyOf(suggestions, N);
mFlags = flags;
@@ -89,14 +100,26 @@
} else {
mLocaleString = locale.toString();
}
- mOriginalString = originalString;
+ mNotificationTargetClass = notificationTargetClass;
+ mHashCode = hashCodeInternal(
+ mFlags, mSuggestions, mLocaleString, mNotificationTargetClass);
}
public SuggestionSpan(Parcel src) {
mSuggestions = src.readStringArray();
mFlags = src.readInt();
mLocaleString = src.readString();
- mOriginalString = src.readString();
+ Class<?> tempClass = null;
+ try {
+ final String className = src.readString();
+ if (!TextUtils.isEmpty(className)) {
+ tempClass = Class.forName(className);
+ }
+ } catch (ClassNotFoundException e) {
+ Log.i(TAG, "Invalid class name was created.");
+ }
+ mNotificationTargetClass = tempClass;
+ mHashCode = src.readInt();
}
/**
@@ -114,10 +137,13 @@
}
/**
- * @return original string of suggestions
+ * @return The class to notify. The class of the original IME package will receive
+ * a notification when the user selects one of the suggestions. The notification will include
+ * the original string, the suggested replacement string as well as the hashCode of this span.
+ * The class will get notified by an intent that has those information.
*/
- public String getOriginalString() {
- return mOriginalString;
+ public Class<?> getNotificationTargetClass() {
+ return mNotificationTargetClass;
}
public int getFlags() {
@@ -134,7 +160,10 @@
dest.writeStringArray(mSuggestions);
dest.writeInt(mFlags);
dest.writeString(mLocaleString);
- dest.writeString(mOriginalString);
+ dest.writeString(mNotificationTargetClass != null
+ ? mNotificationTargetClass.getCanonicalName()
+ : "");
+ dest.writeInt(mHashCode);
}
@Override
@@ -142,6 +171,20 @@
return TextUtils.SUGGESTION_SPAN;
}
+ @Override
+ public int hashCode() {
+ return mHashCode;
+ }
+
+ private static int hashCodeInternal(int flags, String[] suggestions,String locale,
+ Class<?> notificationTargetClass) {
+ final String cls = notificationTargetClass != null
+ ? notificationTargetClass.getCanonicalName()
+ : "";
+ return Arrays.hashCode(
+ new Object[] {SystemClock.uptimeMillis(), flags, suggestions, locale, cls});
+ }
+
public static final Parcelable.Creator<SuggestionSpan> CREATOR =
new Parcelable.Creator<SuggestionSpan>() {
@Override
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index b4303f4..abe3c2c 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -49,8 +49,9 @@
private static final boolean DEBUG = false;
private static final String TAG = "BaseInputConnection";
static final Object COMPOSING = new ComposingText();
-
- final InputMethodManager mIMM;
+
+ /** @hide */
+ protected final InputMethodManager mIMM;
final View mTargetView;
final boolean mDummyMode;
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 27cbaf7..ea66d67 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -35,6 +35,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.text.style.SuggestionSpan;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Printer;
@@ -550,7 +551,25 @@
public void setFullscreenMode(boolean fullScreen) {
mFullscreenMode = fullScreen;
}
-
+
+ /** @hide */
+ public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
+ try {
+ mService.registerSuggestionSpansForNotification(spans);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** @hide */
+ public void notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
+ try {
+ mService.notifySuggestionPicked(span, originalString, index);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
/**
* Allows you to discover whether the attached input method is running
* in fullscreen mode. Return true if it is fullscreen, entirely covering
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 4ffa4e1..8039fda 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -17,6 +17,7 @@
package com.android.internal.view;
import android.os.ResultReceiver;
+import android.text.style.SuggestionSpan;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.EditorInfo;
@@ -61,6 +62,8 @@
void showMySoftInput(in IBinder token, int flags);
void updateStatusIcon(in IBinder token, String packageName, int iconId);
void setImeWindowStatus(in IBinder token, int vis, int backDisposition);
+ void registerSuggestionSpansForNotification(in SuggestionSpan[] spans);
+ boolean notifySuggestionPicked(in SuggestionSpan span, String originalString, int index);
InputMethodSubtype getCurrentInputMethodSubtype();
boolean setCurrentInputMethodSubtype(in InputMethodSubtype subtype);
boolean switchToLastInputMethod(in IBinder token);
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
index 0d32d4ba..32e733b 100644
--- a/core/java/com/android/internal/widget/EditableInputConnection.java
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -17,9 +17,10 @@
package com.android.internal.widget;
import android.os.Bundle;
-import android.os.IBinder;
import android.text.Editable;
+import android.text.Spanned;
import android.text.method.KeyListener;
+import android.text.style.SuggestionSpan;
import android.util.Log;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
@@ -138,6 +139,11 @@
if (mTextView == null) {
return super.commitText(text, newCursorPosition);
}
+ if (text instanceof Spanned) {
+ Spanned spanned = ((Spanned) text);
+ SuggestionSpan[] spans = spanned.getSpans(0, text.length(), SuggestionSpan.class);
+ mIMM.registerSuggestionSpansForNotification(spans);
+ }
mTextView.resetErrorChangedFlag();
boolean success = super.commitText(text, newCursorPosition);
diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h
index 43b2fa9..2b31462 100644
--- a/include/gui/SurfaceTexture.h
+++ b/include/gui/SurfaceTexture.h
@@ -197,8 +197,9 @@
mEglDisplay(EGL_NO_DISPLAY),
mBufferState(BufferSlot::FREE),
mRequestBufferCalled(false),
- mLastQueuedTransform(0),
- mLastQueuedTimestamp(0) {
+ mTransform(0),
+ mTimestamp(0) {
+ mCrop.makeInvalid();
}
// mGraphicBuffer points to the buffer allocated for this slot or is NULL
@@ -211,32 +212,56 @@
// mEglDisplay is the EGLDisplay used to create mEglImage.
EGLDisplay mEglDisplay;
- // mBufferState indicates whether the slot is currently accessible to a
- // client and should not be used by the SurfaceTexture object. It gets
- // set to true when dequeueBuffer returns the slot and is reset to false
- // when the client calls either queueBuffer or cancelBuffer on the slot.
- enum { DEQUEUED=-2, FREE=-1, QUEUED=0 };
- int8_t mBufferState;
+ // BufferState represents the different states in which a buffer slot
+ // can be.
+ enum BufferState {
+ // FREE indicates that the buffer is not currently being used and
+ // will not be used in the future until it gets dequeued and
+ // subseqently queued by the client.
+ FREE = 0,
+ // DEQUEUED indicates that the buffer has been dequeued by the
+ // client, but has not yet been queued or canceled. The buffer is
+ // considered 'owned' by the client, and the server should not use
+ // it for anything.
+ //
+ // Note that when in synchronous-mode (mSynchronousMode == true),
+ // the buffer that's currently attached to the texture may be
+ // dequeued by the client. That means that the current buffer can
+ // be in either the DEQUEUED or QUEUED state. In asynchronous mode,
+ // however, the current buffer is always in the QUEUED state.
+ DEQUEUED = 1,
+
+ // QUEUED indicates that the buffer has been queued by the client,
+ // and has not since been made available for the client to dequeue.
+ // Attaching the buffer to the texture does NOT transition the
+ // buffer away from the QUEUED state. However, in Synchronous mode
+ // the current buffer may be dequeued by the client under some
+ // circumstances. See the note about the current buffer in the
+ // documentation for DEQUEUED.
+ QUEUED = 2,
+ };
+
+ // mBufferState is the current state of this buffer slot.
+ BufferState mBufferState;
// mRequestBufferCalled is used for validating that the client did
// call requestBuffer() when told to do so. Technically this is not
// needed but useful for debugging and catching client bugs.
bool mRequestBufferCalled;
- // mLastQueuedCrop is the crop rectangle for the buffer that was most
- // recently queued. This gets set to mNextCrop each time queueBuffer gets
- // called.
- Rect mLastQueuedCrop;
+ // mCrop is the current crop rectangle for this buffer slot. This gets
+ // set to mNextCrop each time queueBuffer gets called for this buffer.
+ Rect mCrop;
- // mLastQueuedTransform is the transform identifier for the buffer that was
- // most recently queued. This gets set to mNextTransform each time
- // queueBuffer gets called.
- uint32_t mLastQueuedTransform;
+ // mTransform is the current transform flags for this buffer slot. This
+ // gets set to mNextTransform each time queueBuffer gets called for this
+ // slot.
+ uint32_t mTransform;
- // mLastQueuedTimestamp is the timestamp for the buffer that was most
- // recently queued. This gets set by queueBuffer.
- int64_t mLastQueuedTimestamp;
+ // mTimestamp is the current timestamp for this buffer slot. This gets
+ // to set by queueBuffer each time this slot is queued.
+ int64_t mTimestamp;
};
// mSlots is the array of buffer slots that must be mirrored on the client
diff --git a/include/media/mediaplayer.h b/include/media/mediaplayer.h
index cfa4cfd..50a378f 100644
--- a/include/media/mediaplayer.h
+++ b/include/media/mediaplayer.h
@@ -127,6 +127,7 @@
enum media_set_parameter_keys {
KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX = 1000,
+ KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE = 1001,
};
// ----------------------------------------------------------------------------
// ref-counted object for callbacks
diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp
index b08a5a8..ee97dcf 100644
--- a/libs/gui/SurfaceTexture.cpp
+++ b/libs/gui/SurfaceTexture.cpp
@@ -285,15 +285,19 @@
return -EINVAL;
}
- // make sure the client is not trying to dequeue more buffers
- // than allowed.
- const int avail = mBufferCount - (dequeuedCount+1);
- if (avail < (MIN_UNDEQUEUED_BUFFERS-int(mSynchronousMode))) {
- LOGE("dequeueBuffer: MIN_UNDEQUEUED_BUFFERS=%d exceeded (dequeued=%d)",
- MIN_UNDEQUEUED_BUFFERS-int(mSynchronousMode),
- dequeuedCount);
- // TODO: Enable this error report after we fix issue 4435022
- // return -EBUSY;
+ // See whether a buffer has been queued since the last setBufferCount so
+ // we know whether to perform the MIN_UNDEQUEUED_BUFFERS check below.
+ bool bufferHasBeenQueued = mCurrentTexture != INVALID_BUFFER_SLOT;
+ if (bufferHasBeenQueued) {
+ // make sure the client is not trying to dequeue more buffers
+ // than allowed.
+ const int avail = mBufferCount - (dequeuedCount+1);
+ if (avail < (MIN_UNDEQUEUED_BUFFERS-int(mSynchronousMode))) {
+ LOGE("dequeueBuffer: MIN_UNDEQUEUED_BUFFERS=%d exceeded (dequeued=%d)",
+ MIN_UNDEQUEUED_BUFFERS-int(mSynchronousMode),
+ dequeuedCount);
+ return -EBUSY;
+ }
}
// we're in synchronous mode and didn't find a buffer, we need to wait
@@ -390,49 +394,49 @@
sp<FrameAvailableListener> listener;
{ // scope for the lock
- Mutex::Autolock lock(mMutex);
- if (buf < 0 || buf >= mBufferCount) {
- LOGE("queueBuffer: slot index out of range [0, %d]: %d",
- mBufferCount, buf);
- return -EINVAL;
- } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) {
- LOGE("queueBuffer: slot %d is not owned by the client (state=%d)",
- buf, mSlots[buf].mBufferState);
- return -EINVAL;
- } else if (buf == mCurrentTexture) {
- LOGE("queueBuffer: slot %d is current!", buf);
- return -EINVAL;
- } else if (!mSlots[buf].mRequestBufferCalled) {
- LOGE("queueBuffer: slot %d was enqueued without requesting a buffer",
- buf);
- return -EINVAL;
- }
+ Mutex::Autolock lock(mMutex);
+ if (buf < 0 || buf >= mBufferCount) {
+ LOGE("queueBuffer: slot index out of range [0, %d]: %d",
+ mBufferCount, buf);
+ return -EINVAL;
+ } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) {
+ LOGE("queueBuffer: slot %d is not owned by the client (state=%d)",
+ buf, mSlots[buf].mBufferState);
+ return -EINVAL;
+ } else if (buf == mCurrentTexture) {
+ LOGE("queueBuffer: slot %d is current!", buf);
+ return -EINVAL;
+ } else if (!mSlots[buf].mRequestBufferCalled) {
+ LOGE("queueBuffer: slot %d was enqueued without requesting a "
+ "buffer", buf);
+ return -EINVAL;
+ }
- if (mQueue.empty()) {
- listener = mFrameAvailableListener;
- }
-
- if (mSynchronousMode) {
- // in synchronous mode we queue all buffers in a FIFO
- mQueue.push_back(buf);
- } else {
- // in asynchronous mode we only keep the most recent buffer
if (mQueue.empty()) {
+ listener = mFrameAvailableListener;
+ }
+
+ if (mSynchronousMode) {
+ // in synchronous mode we queue all buffers in a FIFO
mQueue.push_back(buf);
} else {
- Fifo::iterator front(mQueue.begin());
- // buffer currently queued is freed
- mSlots[*front].mBufferState = BufferSlot::FREE;
- // and we record the new buffer index in the queued list
- *front = buf;
+ // in asynchronous mode we only keep the most recent buffer
+ if (mQueue.empty()) {
+ mQueue.push_back(buf);
+ } else {
+ Fifo::iterator front(mQueue.begin());
+ // buffer currently queued is freed
+ mSlots[*front].mBufferState = BufferSlot::FREE;
+ // and we record the new buffer index in the queued list
+ *front = buf;
+ }
}
- }
- mSlots[buf].mBufferState = BufferSlot::QUEUED;
- mSlots[buf].mLastQueuedCrop = mNextCrop;
- mSlots[buf].mLastQueuedTransform = mNextTransform;
- mSlots[buf].mLastQueuedTimestamp = timestamp;
- mDequeueCondition.signal();
+ mSlots[buf].mBufferState = BufferSlot::QUEUED;
+ mSlots[buf].mCrop = mNextCrop;
+ mSlots[buf].mTransform = mNextTransform;
+ mSlots[buf].mTimestamp = timestamp;
+ mDequeueCondition.signal();
} // scope for the lock
// call back without lock held
@@ -540,9 +544,9 @@
mCurrentTexture = buf;
mCurrentTextureTarget = target;
mCurrentTextureBuf = mSlots[buf].mGraphicBuffer;
- mCurrentCrop = mSlots[buf].mLastQueuedCrop;
- mCurrentTransform = mSlots[buf].mLastQueuedTransform;
- mCurrentTimestamp = mSlots[buf].mLastQueuedTimestamp;
+ mCurrentCrop = mSlots[buf].mCrop;
+ mCurrentTransform = mSlots[buf].mTransform;
+ mCurrentTimestamp = mSlots[buf].mTimestamp;
mDequeueCondition.signal();
} else {
// We always bind the texture even if we don't update its contents.
@@ -826,12 +830,10 @@
const BufferSlot& slot(mSlots[i]);
snprintf(buffer, SIZE,
"%s%s[%02d] state=%-8s, crop=[%d,%d,%d,%d], transform=0x%02x, "
- "timestamp=%lld\n"
- ,
+ "timestamp=%lld\n",
prefix, (i==mCurrentTexture)?">":" ", i, stateName(slot.mBufferState),
- slot.mLastQueuedCrop.left, slot.mLastQueuedCrop.top,
- slot.mLastQueuedCrop.right, slot.mLastQueuedCrop.bottom,
- slot.mLastQueuedTransform, slot.mLastQueuedTimestamp
+ slot.mCrop.left, slot.mCrop.top, slot.mCrop.right, slot.mCrop.bottom,
+ slot.mTransform, slot.mTimestamp
);
result.append(buffer);
}
diff --git a/libs/gui/SurfaceTextureClient.cpp b/libs/gui/SurfaceTextureClient.cpp
index 6f10320..c20fcf27 100644
--- a/libs/gui/SurfaceTextureClient.cpp
+++ b/libs/gui/SurfaceTextureClient.cpp
@@ -117,7 +117,8 @@
mReqFormat, mReqUsage);
if (result < 0) {
LOGV("dequeueBuffer: ISurfaceTexture::dequeueBuffer(%d, %d, %d, %d)"
- "failed: %d", result, mReqWidth, mReqHeight, mReqFormat, mReqUsage);
+ "failed: %d", mReqWidth, mReqHeight, mReqFormat, mReqUsage,
+ result);
return result;
}
sp<GraphicBuffer>& gbuf(mSlots[buf]);
diff --git a/libs/gui/tests/SurfaceTextureClient_test.cpp b/libs/gui/tests/SurfaceTextureClient_test.cpp
index 59a4cc5..2f704c8 100644
--- a/libs/gui/tests/SurfaceTextureClient_test.cpp
+++ b/libs/gui/tests/SurfaceTextureClient_test.cpp
@@ -400,7 +400,9 @@
EXPECT_EQ(st->getCurrentBuffer().get(), buf[2]);
}
-TEST_F(SurfaceTextureClientTest, SurfaceTextureSyncModeDequeueCurrent) {
+// XXX: We currently have no hardware that properly handles dequeuing the
+// buffer that is currently bound to the texture.
+TEST_F(SurfaceTextureClientTest, DISABLED_SurfaceTextureSyncModeDequeueCurrent) {
sp<ANativeWindow> anw(mSTC);
sp<SurfaceTexture> st(mST);
android_native_buffer_t* buf[3];
@@ -429,10 +431,13 @@
android_native_buffer_t* buf[3];
ASSERT_EQ(OK, st->setSynchronousMode(true));
ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 3));
+
+ // We should be able to dequeue all the buffers before we've queued any.
EXPECT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[0]));
EXPECT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[1]));
- EXPECT_EQ(-EBUSY, anw->dequeueBuffer(anw.get(), &buf[2]));
+ EXPECT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[2]));
+ ASSERT_EQ(OK, anw->cancelBuffer(anw.get(), buf[2]));
ASSERT_EQ(OK, anw->queueBuffer(anw.get(), buf[1]));
EXPECT_EQ(OK, st->updateTexImage());
@@ -440,11 +445,17 @@
EXPECT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[2]));
+ // Once we've queued a buffer, however we should not be able to dequeue more
+ // than (buffer-count - MIN_UNDEQUEUED_BUFFERS), which is 2 in this case.
+ EXPECT_EQ(-EBUSY, anw->dequeueBuffer(anw.get(), &buf[1]));
+
ASSERT_EQ(OK, anw->cancelBuffer(anw.get(), buf[0]));
ASSERT_EQ(OK, anw->cancelBuffer(anw.get(), buf[2]));
}
-TEST_F(SurfaceTextureClientTest, SurfaceTextureSyncModeWaitRetire) {
+// XXX: This is not expected to pass until the synchronization hacks are removed
+// from the SurfaceTexture class.
+TEST_F(SurfaceTextureClientTest, DISABLED_SurfaceTextureSyncModeWaitRetire) {
sp<ANativeWindow> anw(mSTC);
sp<SurfaceTexture> st(mST);
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 4e77fcb..0876bbf 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -1437,9 +1437,7 @@
if (mBluetoothHeadsetDevice != null) {
if (mBluetoothHeadset != null) {
if (!mBluetoothHeadset.stopVoiceRecognition(
- mBluetoothHeadsetDevice) ||
- !mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
- mBluetoothHeadsetDevice)) {
+ mBluetoothHeadsetDevice)) {
sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, 0,
SENDMSG_REPLACE, 0, 0, null, 0);
}
@@ -1506,8 +1504,6 @@
break;
case SCO_STATE_DEACTIVATE_EXT_REQ:
status = mBluetoothHeadset.stopVoiceRecognition(
- mBluetoothHeadsetDevice) &&
- mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
mBluetoothHeadsetDevice);
}
}
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 0e161a8..8f7dd60 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1253,14 +1253,26 @@
*/
public native void attachAuxEffect(int effectId);
- /* Do not change these values without updating their counterparts
- * in include/media/mediaplayer.h!
+ /* Do not change these values (starting with KEY_PARAMETER) without updating
+ * their counterparts in include/media/mediaplayer.h!
*/
- /**
+ /*
* Key used in setParameter method.
- * Indicates the index of the timed text track to be enabled/disabled
+ * Indicates the index of the timed text track to be enabled/disabled.
+ * The index includes both the in-band and out-of-band timed text.
+ * The index should start from in-band text if any. Application can retrieve the number
+ * of in-band text tracks by using MediaMetadataRetriever::extractMetadata().
+ * Note it might take a few hundred ms to scan an out-of-band text file
+ * before displaying it.
*/
private static final int KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX = 1000;
+ /*
+ * Key used in setParameter method.
+ * Used to add out-of-band timed text source path.
+ * Application can add multiple text sources by calling setParameter() with
+ * KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE multiple times.
+ */
+ private static final int KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE = 1001;
/**
* Sets the parameter indicated by key.
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index f731dfb..8c4b274 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -50,7 +50,6 @@
ThrottledSource.cpp \
TimeSource.cpp \
TimedEventQueue.cpp \
- TimedTextPlayer.cpp \
Utils.cpp \
VBRISeeker.cpp \
WAVExtractor.cpp \
@@ -89,6 +88,7 @@
libstagefright_avcenc \
libstagefright_m4vh263enc \
libstagefright_matroska \
+ libstagefright_timedtext \
libvpx \
libstagefright_mpeg2ts \
libstagefright_httplive \
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 70053ea..3d270f89 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -29,9 +29,10 @@
#include "include/NuCachedSource2.h"
#include "include/ThrottledSource.h"
#include "include/MPEG2TSExtractor.h"
-#include "include/TimedTextPlayer.h"
#include "include/WVMExtractor.h"
+#include "timedtext/TimedTextPlayer.h"
+
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <media/IMediaPlayerService.h>
@@ -1282,6 +1283,7 @@
}
void AwesomePlayer::addTextSource(sp<MediaSource> source) {
+ Mutex::Autolock autoLock(mTimedTextLock);
CHECK(source != NULL);
if (mTextPlayer == NULL) {
@@ -2066,10 +2068,26 @@
}
status_t AwesomePlayer::setParameter(int key, const Parcel &request) {
- if (key == KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX) {
- return setTimedTextTrackIndex(request.readInt32());
+ switch (key) {
+ case KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX:
+ {
+ Mutex::Autolock autoLock(mTimedTextLock);
+ return setTimedTextTrackIndex(request.readInt32());
+ }
+ case KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE:
+ {
+ Mutex::Autolock autoLock(mTimedTextLock);
+ if (mTextPlayer == NULL) {
+ mTextPlayer = new TimedTextPlayer(this, mListener, &mQueue);
+ }
+
+ return mTextPlayer->setParameter(key, request);
+ }
+ default:
+ {
+ return ERROR_UNSUPPORTED;
+ }
}
- return ERROR_UNSUPPORTED;
}
status_t AwesomePlayer::getParameter(int key, Parcel *reply) {
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 6692809..98ac044 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -20,7 +20,7 @@
#include "include/MPEG4Extractor.h"
#include "include/SampleTable.h"
#include "include/ESDS.h"
-#include "include/TimedTextPlayer.h"
+#include "timedtext/TimedTextPlayer.h"
#include <arpa/inet.h>
diff --git a/media/libstagefright/TimedTextPlayer.cpp b/media/libstagefright/TimedTextPlayer.cpp
deleted file mode 100644
index 1ac22cb..0000000
--- a/media/libstagefright/TimedTextPlayer.cpp
+++ /dev/null
@@ -1,252 +0,0 @@
- /*
- * 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.
- */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "TimedTextPlayer"
-#include <utils/Log.h>
-
-#include <binder/IPCThreadState.h>
-#include <media/stagefright/MediaDebug.h>
-#include <media/stagefright/MediaDefs.h>
-#include <media/stagefright/MediaErrors.h>
-#include <media/stagefright/MediaSource.h>
-#include <media/stagefright/MetaData.h>
-#include <media/stagefright/MediaBuffer.h>
-#include <media/stagefright/Utils.h>
-#include "include/AwesomePlayer.h"
-#include "include/TimedTextPlayer.h"
-
-namespace android {
-
-struct TimedTextEvent : public TimedEventQueue::Event {
- TimedTextEvent(
- TimedTextPlayer *player,
- void (TimedTextPlayer::*method)())
- : mPlayer(player),
- mMethod(method) {
- }
-
-protected:
- virtual ~TimedTextEvent() {}
-
- virtual void fire(TimedEventQueue *queue, int64_t /* now_us */) {
- (mPlayer->*mMethod)();
- }
-
-private:
- TimedTextPlayer *mPlayer;
- void (TimedTextPlayer::*mMethod)();
-
- TimedTextEvent(const TimedTextEvent &);
- TimedTextEvent &operator=(const TimedTextEvent &);
-};
-
-TimedTextPlayer::TimedTextPlayer(
- AwesomePlayer *observer,
- const wp<MediaPlayerBase> &listener,
- TimedEventQueue *queue)
- : mSource(NULL),
- mSeekTimeUs(0),
- mStarted(false),
- mTextEventPending(false),
- mQueue(queue),
- mListener(listener),
- mObserver(observer),
- mTextBuffer(NULL) {
- mTextEvent = new TimedTextEvent(this, &TimedTextPlayer::onTextEvent);
-}
-
-TimedTextPlayer::~TimedTextPlayer() {
- if (mStarted) {
- reset();
- }
-
- mTextTrackVector.clear();
-}
-
-status_t TimedTextPlayer::start(uint8_t index) {
- CHECK(!mStarted);
-
- if (index >= mTextTrackVector.size()) {
- LOGE("Incorrect text track index");
- return BAD_VALUE;
- }
-
- mSource = mTextTrackVector.itemAt(index);
-
- status_t err = mSource->start();
-
- if (err != OK) {
- return err;
- }
-
- int64_t positionUs;
- mObserver->getPosition(&positionUs);
- seekTo(positionUs);
-
- postTextEvent();
-
- mStarted = true;
-
- return OK;
-}
-
-void TimedTextPlayer::pause() {
- CHECK(mStarted);
-
- cancelTextEvent();
-}
-
-void TimedTextPlayer::resume() {
- CHECK(mStarted);
-
- postTextEvent();
-}
-
-void TimedTextPlayer::reset() {
- CHECK(mStarted);
-
- // send an empty text to clear the screen
- notifyListener(MEDIA_TIMED_TEXT);
-
- cancelTextEvent();
-
- mSeeking = false;
- mStarted = false;
-
- if (mTextBuffer != NULL) {
- mTextBuffer->release();
- mTextBuffer = NULL;
- }
-
- if (mSource != NULL) {
- mSource->stop();
- mSource.clear();
- mSource = NULL;
- }
-}
-
-status_t TimedTextPlayer::seekTo(int64_t time_us) {
- Mutex::Autolock autoLock(mLock);
-
- mSeeking = true;
- mSeekTimeUs = time_us;
-
- return OK;
-}
-
-status_t TimedTextPlayer::setTimedTextTrackIndex(int32_t index) {
- if (index >= (int)(mTextTrackVector.size())) {
- return BAD_VALUE;
- }
-
- if (mStarted) {
- reset();
- }
-
- if (index >= 0) {
- return start(index);
- }
- return OK;
-}
-
-void TimedTextPlayer::onTextEvent() {
- Mutex::Autolock autoLock(mLock);
-
- if (!mTextEventPending) {
- return;
- }
- mTextEventPending = false;
-
- MediaSource::ReadOptions options;
- if (mSeeking) {
- options.setSeekTo(mSeekTimeUs,
- MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC);
- mSeeking = false;
-
- if (mTextBuffer != NULL) {
- mTextBuffer->release();
- mTextBuffer = NULL;
- }
-
- notifyListener(MEDIA_TIMED_TEXT); //empty text to clear the screen
- }
-
- 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;
- }
-
- int64_t positionUs, timeUs;
- mObserver->getPosition(&positionUs);
- mTextBuffer->meta_data()->findInt64(kKeyTime, &timeUs);
-
- //send the text now
- if (timeUs <= positionUs + 100000ll) {
- postTextEvent();
- } else {
- postTextEvent(timeUs - positionUs - 100000ll);
- }
-}
-
-void TimedTextPlayer::postTextEvent(int64_t delayUs) {
- if (mTextEventPending) {
- return;
- }
-
- mTextEventPending = true;
- mQueue->postEventWithDelay(mTextEvent, delayUs < 0 ? 10000 : delayUs);
-}
-
-void TimedTextPlayer::cancelTextEvent() {
- mQueue->cancelEvent(mTextEvent->eventID());
- mTextEventPending = false;
-}
-
-void TimedTextPlayer::addTextSource(sp<MediaSource> source) {
- mTextTrackVector.add(source);
-}
-
-void TimedTextPlayer::notifyListener(
- int msg, const void *data, size_t size) {
- 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);
- } else { // send an empty timed text to clear the screen
- listener->sendEvent(msg);
- }
- }
- }
-}
-}
diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h
index 3c9a121..a9e8e95 100644
--- a/media/libstagefright/include/AwesomePlayer.h
+++ b/media/libstagefright/include/AwesomePlayer.h
@@ -231,6 +231,7 @@
int64_t mLastVideoTimeUs;
TimedTextPlayer *mTextPlayer;
+ mutable Mutex mTimedTextLock;
sp<WVMExtractor> mWVMExtractor;
diff --git a/media/libstagefright/timedtext/Android.mk b/media/libstagefright/timedtext/Android.mk
new file mode 100644
index 0000000..9a6062c
--- /dev/null
+++ b/media/libstagefright/timedtext/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ TimedTextParser.cpp \
+ TimedTextPlayer.cpp
+
+LOCAL_CFLAGS += -Wno-multichar
+LOCAL_C_INCLUDES:= \
+ $(JNI_H_INCLUDE) \
+ $(TOP)/frameworks/base/media/libstagefright \
+ $(TOP)/frameworks/base/include/media/stagefright/openmax
+
+LOCAL_MODULE:= libstagefright_timedtext
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/media/libstagefright/timedtext/TimedTextParser.cpp b/media/libstagefright/timedtext/TimedTextParser.cpp
new file mode 100644
index 0000000..0bada16
--- /dev/null
+++ b/media/libstagefright/timedtext/TimedTextParser.cpp
@@ -0,0 +1,257 @@
+/*
+ * 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 "TimedTextParser.h"
+#include <media/stagefright/DataSource.h>
+
+namespace android {
+
+TimedTextParser::TimedTextParser()
+ : mDataSource(NULL),
+ mOffset(0),
+ mIndex(0) {
+}
+
+TimedTextParser::~TimedTextParser() {
+ reset();
+}
+
+status_t TimedTextParser::init(
+ const sp<DataSource> &dataSource, FileType fileType) {
+ mDataSource = dataSource;
+ mFileType = fileType;
+
+ status_t err;
+ if ((err = scanFile()) != OK) {
+ reset();
+ return err;
+ }
+
+ return OK;
+}
+
+void TimedTextParser::reset() {
+ mDataSource.clear();
+ mTextVector.clear();
+ mOffset = 0;
+ mIndex = 0;
+}
+
+// scan the text file to get start/stop time and the
+// offset of each piece of text content
+status_t TimedTextParser::scanFile() {
+ if (mFileType != OUT_OF_BAND_FILE_SRT) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ off64_t offset = 0;
+ int64_t startTimeUs;
+ bool endOfFile = false;
+
+ while (!endOfFile) {
+ TextInfo info;
+ status_t err = getNextInSrtFileFormat(&offset, &startTimeUs, &info);
+
+ if (err != OK) {
+ if (err == ERROR_END_OF_STREAM) {
+ endOfFile = true;
+ } else {
+ return err;
+ }
+ } else {
+ mTextVector.add(startTimeUs, info);
+ }
+ }
+
+ if (mTextVector.isEmpty()) {
+ return ERROR_MALFORMED;
+ }
+ return OK;
+}
+
+// read one line started from *offset and store it into data.
+status_t TimedTextParser::readNextLine(off64_t *offset, AString *data) {
+ char character;
+
+ data->clear();
+
+ while (true) {
+ ssize_t err;
+ if ((err = mDataSource->readAt(*offset, &character, 1)) < 1) {
+ if (err == 0) {
+ return ERROR_END_OF_STREAM;
+ }
+ return ERROR_IO;
+ }
+
+ (*offset) ++;
+
+ // a line could end with CR, LF or CR + LF
+ if (character == 10) {
+ break;
+ } else if (character == 13) {
+ if ((err = mDataSource->readAt(*offset, &character, 1)) < 1) {
+ if (err == 0) { // end of the stream
+ return OK;
+ }
+ return ERROR_IO;
+ }
+
+ (*offset) ++;
+
+ if (character != 10) {
+ (*offset) --;
+ }
+ break;
+ }
+
+ data->append(character);
+ }
+
+ return OK;
+}
+
+/* SRT format:
+ * Subtitle number
+ * Start time --> End time
+ * Text of subtitle (one or more lines)
+ * Blank line
+ *
+ * .srt file example:
+ * 1
+ * 00:00:20,000 --> 00:00:24,400
+ * Altocumulus clouds occur between six thousand
+ *
+ * 2
+ * 00:00:24,600 --> 00:00:27,800
+ * and twenty thousand feet above ground level.
+ */
+status_t TimedTextParser::getNextInSrtFileFormat(
+ off64_t *offset, int64_t *startTimeUs, TextInfo *info) {
+ AString data;
+ status_t err;
+ if ((err = readNextLine(offset, &data)) != OK) {
+ return err;
+ }
+
+ // to skip the first line
+ if ((err = readNextLine(offset, &data)) != OK) {
+ return err;
+ }
+
+ int hour1, hour2, min1, min2, sec1, sec2, msec1, msec2;
+ // the start time format is: hours:minutes:seconds,milliseconds
+ // 00:00:24,600 --> 00:00:27,800
+ if (sscanf(data.c_str(), "%02d:%02d:%02d,%03d --> %02d:%02d:%02d,%03d",
+ &hour1, &min1, &sec1, &msec1, &hour2, &min2, &sec2, &msec2) != 8) {
+ return ERROR_MALFORMED;
+ }
+
+ *startTimeUs = ((hour1 * 3600 + min1 * 60 + sec1) * 1000 + msec1) * 1000ll;
+ info->endTimeUs = ((hour2 * 3600 + min2 * 60 + sec2) * 1000 + msec2) * 1000ll;
+ if (info->endTimeUs <= *startTimeUs) {
+ return ERROR_MALFORMED;
+ }
+
+ info->offset = *offset;
+
+ bool needMoreData = true;
+ while (needMoreData) {
+ if ((err = readNextLine(offset, &data)) != OK) {
+ if (err == ERROR_END_OF_STREAM) {
+ needMoreData = false;
+ } else {
+ return err;
+ }
+ }
+
+ if (needMoreData) {
+ data.trim();
+ if (data.empty()) {
+ // it's an empty line used to separate two subtitles
+ needMoreData = false;
+ }
+ }
+ }
+
+ info->textLen = *offset - info->offset;
+
+ return OK;
+}
+
+status_t TimedTextParser::getText(
+ AString *text, int64_t *startTimeUs, int64_t *endTimeUs,
+ const MediaSource::ReadOptions *options) {
+ Mutex::Autolock autoLock(mLock);
+
+ text->clear();
+
+ int64_t seekTimeUs;
+ MediaSource::ReadOptions::SeekMode mode;
+ if (options && options->getSeekTo(&seekTimeUs, &mode)) {
+ int64_t lastEndTimeUs = mTextVector.valueAt(mTextVector.size() - 1).endTimeUs;
+ int64_t firstStartTimeUs = mTextVector.keyAt(0);
+
+ if (seekTimeUs < 0 || seekTimeUs > lastEndTimeUs) {
+ return ERROR_OUT_OF_RANGE;
+ } else if (seekTimeUs < firstStartTimeUs) {
+ mIndex = 0;
+ } else {
+ // binary search
+ ssize_t low = 0;
+ ssize_t high = mTextVector.size() - 1;
+ ssize_t mid = 0;
+ int64_t currTimeUs;
+
+ while (low <= high) {
+ mid = low + (high - low)/2;
+ currTimeUs = mTextVector.keyAt(mid);
+ const int diff = currTimeUs - seekTimeUs;
+
+ if (diff == 0) {
+ break;
+ } else if (diff < 0) {
+ low = mid + 1;
+ } else {
+ if ((high == mid + 1)
+ && (seekTimeUs < mTextVector.keyAt(high))) {
+ break;
+ }
+ high = mid - 1;
+ }
+ }
+
+ mIndex = mid;
+ }
+ }
+
+ TextInfo info = mTextVector.valueAt(mIndex);
+ *startTimeUs = mTextVector.keyAt(mIndex);
+ *endTimeUs = info.endTimeUs;
+ mIndex ++;
+
+ char *str = new char[info.textLen];
+ if (mDataSource->readAt(info.offset, str, info.textLen) < info.textLen) {
+ delete[] str;
+ return ERROR_IO;
+ }
+
+ text->append(str, info.textLen);
+ delete[] str;
+ return OK;
+}
+
+} // namespace android
diff --git a/media/libstagefright/timedtext/TimedTextParser.h b/media/libstagefright/timedtext/TimedTextParser.h
new file mode 100644
index 0000000..44774c2
--- /dev/null
+++ b/media/libstagefright/timedtext/TimedTextParser.h
@@ -0,0 +1,75 @@
+/*
+ * 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 TIMED_TEXT_PARSER_H_
+
+#define TIMED_TEXT_PARSER_H_
+
+#include <media/MediaPlayerInterface.h>
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AString.h>
+#include <media/stagefright/MediaSource.h>
+
+namespace android {
+
+class DataSource;
+
+class TimedTextParser : public RefBase {
+public:
+ TimedTextParser();
+ virtual ~TimedTextParser();
+
+ enum FileType {
+ OUT_OF_BAND_FILE_SRT = 1,
+ };
+
+ status_t getText(AString *text, int64_t *startTimeUs, int64_t *endTimeUs,
+ const MediaSource::ReadOptions *options = NULL);
+ status_t init(const sp<DataSource> &dataSource, FileType fileType);
+ void reset();
+
+private:
+ Mutex mLock;
+
+ sp<DataSource> mDataSource;
+ off64_t mOffset;
+
+ struct TextInfo {
+ int64_t endTimeUs;
+ // the offset of the text in the original file
+ off64_t offset;
+ int textLen;
+ };
+
+ int mIndex;
+ FileType mFileType;
+
+ // the key indicated the start time of the text
+ KeyedVector<int64_t, TextInfo> mTextVector;
+
+ status_t getNextInSrtFileFormat(
+ off64_t *offset, int64_t *startTimeUs, TextInfo *info);
+ status_t readNextLine(off64_t *offset, AString *data);
+
+ status_t scanFile();
+
+ DISALLOW_EVIL_CONSTRUCTORS(TimedTextParser);
+};
+
+} // namespace android
+
+#endif // TIMED_TEXT_PARSER_H_
+
diff --git a/media/libstagefright/timedtext/TimedTextPlayer.cpp b/media/libstagefright/timedtext/TimedTextPlayer.cpp
new file mode 100644
index 0000000..50bb16d
--- /dev/null
+++ b/media/libstagefright/timedtext/TimedTextPlayer.cpp
@@ -0,0 +1,347 @@
+ /*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "TimedTextPlayer"
+#include <utils/Log.h>
+
+#include <binder/IPCThreadState.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#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"
+
+namespace android {
+
+struct TimedTextEvent : public TimedEventQueue::Event {
+ TimedTextEvent(
+ TimedTextPlayer *player,
+ void (TimedTextPlayer::*method)())
+ : mPlayer(player),
+ mMethod(method) {
+ }
+
+protected:
+ virtual ~TimedTextEvent() {}
+
+ virtual void fire(TimedEventQueue *queue, int64_t /* now_us */) {
+ (mPlayer->*mMethod)();
+ }
+
+private:
+ TimedTextPlayer *mPlayer;
+ void (TimedTextPlayer::*mMethod)();
+
+ TimedTextEvent(const TimedTextEvent &);
+ TimedTextEvent &operator=(const TimedTextEvent &);
+};
+
+TimedTextPlayer::TimedTextPlayer(
+ AwesomePlayer *observer,
+ const wp<MediaPlayerBase> &listener,
+ TimedEventQueue *queue)
+ : mSource(NULL),
+ mOutOfBandSource(NULL),
+ mSeekTimeUs(0),
+ mStarted(false),
+ mTextEventPending(false),
+ mQueue(queue),
+ mListener(listener),
+ mObserver(observer),
+ mTextBuffer(NULL),
+ mTextParser(NULL),
+ mTextType(kNoText) {
+ mTextEvent = new TimedTextEvent(this, &TimedTextPlayer::onTextEvent);
+}
+
+TimedTextPlayer::~TimedTextPlayer() {
+ if (mStarted) {
+ reset();
+ }
+
+ mTextTrackVector.clear();
+ mTextOutOfBandVector.clear();
+}
+
+status_t TimedTextPlayer::start(uint8_t index) {
+ CHECK(!mStarted);
+
+ if (index >=
+ mTextTrackVector.size() + mTextOutOfBandVector.size()) {
+ LOGE("Incorrect text track index: %d", index);
+ return BAD_VALUE;
+ }
+
+ if (index < mTextTrackVector.size()) { // start an in-band text
+ mSource = mTextTrackVector.itemAt(index);
+
+ status_t err = mSource->start();
+
+ if (err != OK) {
+ return err;
+ }
+ mTextType = kInBandText;
+ } else { // start an out-of-band text
+ OutOfBandText text =
+ mTextOutOfBandVector.itemAt(index - mTextTrackVector.size());
+
+ mOutOfBandSource = text.source;
+ TimedTextParser::FileType fileType = text.type;
+
+ if (mTextParser == NULL) {
+ mTextParser = new TimedTextParser();
+ }
+
+ status_t err;
+ if ((err = mTextParser->init(mOutOfBandSource, fileType)) != OK) {
+ return err;
+ }
+ mTextType = kOutOfBandText;
+ }
+
+ int64_t positionUs;
+ mObserver->getPosition(&positionUs);
+ seekTo(positionUs);
+
+ postTextEvent();
+
+ mStarted = true;
+
+ return OK;
+}
+
+void TimedTextPlayer::pause() {
+ CHECK(mStarted);
+
+ cancelTextEvent();
+}
+
+void TimedTextPlayer::resume() {
+ CHECK(mStarted);
+
+ postTextEvent();
+}
+
+void TimedTextPlayer::reset() {
+ CHECK(mStarted);
+
+ // send an empty text to clear the screen
+ notifyListener(MEDIA_TIMED_TEXT);
+
+ cancelTextEvent();
+
+ mSeeking = false;
+ mStarted = false;
+
+ if (mTextType == kInBandText) {
+ if (mTextBuffer != NULL) {
+ mTextBuffer->release();
+ mTextBuffer = NULL;
+ }
+
+ if (mSource != NULL) {
+ mSource->stop();
+ mSource.clear();
+ mSource = NULL;
+ }
+ } else {
+ if (mTextParser != NULL) {
+ mTextParser.clear();
+ mTextParser = NULL;
+ }
+ if (mOutOfBandSource != NULL) {
+ mOutOfBandSource.clear();
+ mOutOfBandSource = NULL;
+ }
+ }
+}
+
+status_t TimedTextPlayer::seekTo(int64_t time_us) {
+ Mutex::Autolock autoLock(mLock);
+
+ mSeeking = true;
+ mSeekTimeUs = time_us;
+
+ postTextEvent();
+
+ return OK;
+}
+
+status_t TimedTextPlayer::setTimedTextTrackIndex(int32_t index) {
+ if (index >=
+ (int)(mTextTrackVector.size() + mTextOutOfBandVector.size())) {
+ return BAD_VALUE;
+ }
+
+ if (mStarted) {
+ reset();
+ }
+
+ if (index >= 0) {
+ return start(index);
+ }
+ return OK;
+}
+
+void TimedTextPlayer::onTextEvent() {
+ Mutex::Autolock autoLock(mLock);
+
+ if (!mTextEventPending) {
+ return;
+ }
+ mTextEventPending = false;
+
+ 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
+ }
+
+ int64_t positionUs, timeUs;
+ 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) {
+ return;
+ }
+ }
+
+ //send the text now
+ if (timeUs <= positionUs + 100000ll) {
+ postTextEvent();
+ } else {
+ postTextEvent(timeUs - positionUs - 100000ll);
+ }
+}
+
+void TimedTextPlayer::postTextEvent(int64_t delayUs) {
+ if (mTextEventPending) {
+ return;
+ }
+
+ mTextEventPending = true;
+ mQueue->postEventWithDelay(mTextEvent, delayUs < 0 ? 10000 : delayUs);
+}
+
+void TimedTextPlayer::cancelTextEvent() {
+ mQueue->cancelEvent(mTextEvent->eventID());
+ mTextEventPending = false;
+}
+
+void TimedTextPlayer::addTextSource(sp<MediaSource> source) {
+ Mutex::Autolock autoLock(mLock);
+ mTextTrackVector.add(source);
+}
+
+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();
+ KeyedVector<String8, String8> headers;
+
+ // To support local subtitle file only for now
+ if (strncasecmp("file://", uri.string(), 7)) {
+ return INVALID_OPERATION;
+ }
+ sp<DataSource> dataSource =
+ DataSource::CreateFromURI(uri, &headers);
+ status_t err = dataSource->initCheck();
+
+ if (err != OK) {
+ return err;
+ }
+
+ OutOfBandText text;
+ text.source = dataSource;
+ if (uri.getPathExtension() == String8(".srt")) {
+ text.type = TimedTextParser::OUT_OF_BAND_FILE_SRT;
+ } else {
+ return ERROR_UNSUPPORTED;
+ }
+
+ mTextOutOfBandVector.add(text);
+
+ return OK;
+ }
+ return INVALID_OPERATION;
+}
+
+void TimedTextPlayer::notifyListener(
+ int msg, const void *data, size_t size) {
+ 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);
+ } else { // send an empty timed text to clear the screen
+ listener->sendEvent(msg);
+ }
+ }
+ }
+}
+}
diff --git a/media/libstagefright/include/TimedTextPlayer.h b/media/libstagefright/timedtext/TimedTextPlayer.h
similarity index 77%
rename from media/libstagefright/include/TimedTextPlayer.h
rename to media/libstagefright/timedtext/TimedTextPlayer.h
index ac41b4f..590760b 100644
--- a/media/libstagefright/include/TimedTextPlayer.h
+++ b/media/libstagefright/timedtext/TimedTextPlayer.h
@@ -20,8 +20,10 @@
#include <media/MediaPlayerInterface.h>
#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AString.h>
#include "include/TimedEventQueue.h"
+#include "TimedTextParser.h"
namespace android {
@@ -50,11 +52,19 @@
void addTextSource(sp<MediaSource> source);
status_t setTimedTextTrackIndex(int32_t index);
+ status_t setParameter(int key, const Parcel &request);
private:
+ enum TextType {
+ kNoText = 0,
+ kInBandText = 1,
+ kOutOfBandText = 2,
+ };
+
Mutex mLock;
sp<MediaSource> mSource;
+ sp<DataSource> mOutOfBandSource;
bool mSeeking;
int64_t mSeekTimeUs;
@@ -72,8 +82,21 @@
MediaBuffer *mTextBuffer;
Parcel mData;
+ // for in-band timed text
Vector<sp<MediaSource> > mTextTrackVector;
+ // for out-of-band timed text
+ struct OutOfBandText {
+ TimedTextParser::FileType type;
+ sp<DataSource> source;
+ };
+ Vector<OutOfBandText > mTextOutOfBandVector;
+
+ sp<TimedTextParser> mTextParser;
+ AString mText;
+
+ TextType mTextType;
+
void reset();
void onTextEvent();
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index c365af5..7361ef7 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -64,7 +64,9 @@
import android.provider.Settings.Secure;
import android.provider.Settings.SettingNotFoundException;
import android.text.TextUtils;
+import android.text.style.SuggestionSpan;
import android.util.EventLog;
+import android.util.LruCache;
import android.util.Pair;
import android.util.Slog;
import android.util.PrintWriterPrinter;
@@ -117,6 +119,8 @@
static final long TIME_TO_RECONNECT = 10*1000;
+ static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
+
private static final int NOT_A_SUBTYPE_ID = -1;
private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
@@ -141,6 +145,8 @@
// lock for this class.
final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>();
final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>();
+ private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans =
+ new LruCache<SuggestionSpan, InputMethodInfo>(SECURE_SUGGESTION_SPANS_MAX_SIZE);
class SessionState {
final ClientState client;
@@ -965,6 +971,7 @@
}
}
+ @Override
public void updateStatusIcon(IBinder token, String packageName, int iconId) {
int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
@@ -989,6 +996,7 @@
}
}
+ @Override
public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
@@ -1008,6 +1016,41 @@
}
}
+ public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
+ synchronized (mMethodMap) {
+ final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
+ for (int i = 0; i < spans.length; ++i) {
+ SuggestionSpan ss = spans[i];
+ if (ss.getNotificationTargetClass() != null) {
+ mSecureSuggestionSpans.put(ss, currentImi);
+ }
+ }
+ }
+ }
+
+ public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
+ synchronized (mMethodMap) {
+ final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
+ // TODO: Do not send the intent if the process of the targetImi is already dead.
+ if (targetImi != null) {
+ final String[] suggestions = span.getSuggestions();
+ if (index < 0 || index >= suggestions.length) return false;
+ final Class<?> c = span.getNotificationTargetClass();
+ final Intent intent = new Intent();
+ // Ensures that only a class in the original IME package will receive the
+ // notification.
+ intent.setClassName(targetImi.getPackageName(), c.getCanonicalName());
+ intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
+ intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString);
+ intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]);
+ intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode());
+ mContext.sendBroadcast(intent);
+ return true;
+ }
+ }
+ return false;
+ }
+
void updateFromSettingsLocked() {
// We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
// ENABLED_INPUT_METHODS is taking care of keeping them correctly in
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index c1aa215..35f2b9b 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -376,7 +376,7 @@
<activity
android:name="ListActivity"
- android:label="_List">
+ android:label="__List">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />