Merge change 3013 into donut
* changes:
fix a bug in GL lighting where the specular component could be ommited when vertex material was disabled.
diff --git a/api/current.xml b/api/current.xml
index 3a31034..9e9eaad4 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -1145,6 +1145,17 @@
visibility="public"
>
</field>
+<field name="WRITE_EXTERNAL_STORAGE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.permission.WRITE_EXTERNAL_STORAGE""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="WRITE_GSERVICES"
type="java.lang.String"
transient="false"
@@ -1167,17 +1178,6 @@
visibility="public"
>
</field>
-<field name="WRITE_EXTERNAL_STORAGE"
- type="java.lang.String"
- transient="false"
- volatile="false"
- value=""android.permission.WRITE_EXTERNAL_STORAGE""
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
<field name="WRITE_SECURE_SETTINGS"
type="java.lang.String"
transient="false"
@@ -46738,6 +46738,19 @@
<parameter name="listener" type="android.gesture.GestureOverlayView.OnGesturePerformedListener">
</parameter>
</method>
+<method name="addOnGesturingListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.gesture.GestureOverlayView.OnGesturingListener">
+</parameter>
+</method>
<method name="cancelClearAnimation"
return="void"
abstract="false"
@@ -46938,6 +46951,17 @@
visibility="public"
>
</method>
+<method name="removeAllOnGesturingListeners"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="removeOnGestureListener"
return="void"
abstract="false"
@@ -46964,6 +46988,19 @@
<parameter name="listener" type="android.gesture.GestureOverlayView.OnGesturePerformedListener">
</parameter>
</method>
+<method name="removeOnGesturingListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.gesture.GestureOverlayView.OnGesturingListener">
+</parameter>
+</method>
<method name="setEventsInterceptionEnabled"
return="void"
abstract="false"
@@ -47243,6 +47280,40 @@
</parameter>
</method>
</interface>
+<interface name="GestureOverlayView.OnGesturingListener"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onGesturingEnded"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="overlay" type="android.gesture.GestureOverlayView">
+</parameter>
+</method>
+<method name="onGesturingStarted"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="overlay" type="android.gesture.GestureOverlayView">
+</parameter>
+</method>
+</interface>
<class name="GesturePoint"
extends="java.lang.Object"
abstract="false"
diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java
index 2c2310a..2d6381a 100644
--- a/core/java/android/app/ApplicationContext.java
+++ b/core/java/android/app/ApplicationContext.java
@@ -2425,6 +2425,16 @@
}
@Override
+ public void replacePreferredActivity(IntentFilter filter,
+ int match, ComponentName[] set, ComponentName activity) {
+ try {
+ mPM.replacePreferredActivity(filter, match, set, activity);
+ } catch (RemoteException e) {
+ // Should never happen!
+ }
+ }
+
+ @Override
public void clearPackagePreferredActivities(String packageName) {
try {
mPM.clearPackagePreferredActivities(packageName);
diff --git a/core/java/android/app/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java
index 451697a..747bec9 100644
--- a/core/java/android/app/SuggestionsAdapter.java
+++ b/core/java/android/app/SuggestionsAdapter.java
@@ -32,12 +32,13 @@
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.CursorAdapter;
import android.widget.ImageView;
import android.widget.ResourceCursorAdapter;
import android.widget.TextView;
import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.WeakHashMap;
/**
@@ -376,9 +377,18 @@
// Let the ContentResolver handle content, android.resource and file URIs.
try {
Uri uri = Uri.parse(drawableId);
- drawable = Drawable.createFromStream(
- mProviderContext.getContentResolver().openInputStream(uri),
- null);
+ InputStream stream = mProviderContext.getContentResolver().openInputStream(uri);
+ if (stream != null) {
+ try {
+ drawable = Drawable.createFromStream(stream, null);
+ } finally {
+ try {
+ stream.close();
+ } catch (IOException ex) {
+ Log.e(LOG_TAG, "Error closing icon stream for " + uri, ex);
+ }
+ }
+ }
if (DBG) Log.d(LOG_TAG, "Opened icon input stream: " + drawableId);
} catch (FileNotFoundException fnfe) {
if (DBG) Log.d(LOG_TAG, "Icon stream not found: " + drawableId);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index bb913cd..5f62248 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -167,7 +167,12 @@
void addPreferredActivity(in IntentFilter filter, int match,
in ComponentName[] set, in ComponentName activity);
+
+ void replacePreferredActivity(in IntentFilter filter, int match,
+ in ComponentName[] set, in ComponentName activity);
+
void clearPackagePreferredActivities(String packageName);
+
int getPreferredActivities(out List<IntentFilter> outFilters,
out List<ComponentName> outActivities, String packageName);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 238a98a..a2c82e8 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1630,6 +1630,26 @@
ComponentName[] set, ComponentName activity);
/**
+ * Replaces an existing preferred activity mapping to the system, and if that were not present
+ * adds a new preferred activity. This will be used
+ * to automatically select the given activity component when
+ * {@link Context#startActivity(Intent) Context.startActivity()} finds
+ * multiple matching activities and also matches the given filter.
+ *
+ * @param filter The set of intents under which this activity will be
+ * made preferred.
+ * @param match The IntentFilter match category that this preference
+ * applies to.
+ * @param set The set of activities that the user was picking from when
+ * this preference was made.
+ * @param activity The component name of the activity that is to be
+ * preferred.
+ * @hide
+ */
+ public abstract void replacePreferredActivity(IntentFilter filter, int match,
+ ComponentName[] set, ComponentName activity);
+
+ /**
* Remove all preferred activity mappings, previously added with
* {@link #addPreferredActivity}, from the
* system whose activities are implemented in the given package name.
diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
index 231e3e2..a37e4e8 100644
--- a/core/java/android/content/res/AssetFileDescriptor.java
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -16,6 +16,7 @@
package android.content.res;
+import android.os.MemoryFile;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
@@ -24,6 +25,8 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.FileChannel;
/**
* File descriptor of an entry in the AssetManager. This provides your own
@@ -124,6 +127,13 @@
}
/**
+ * Checks whether this file descriptor is for a memory file.
+ */
+ private boolean isMemoryFile() throws IOException {
+ return MemoryFile.isMemoryFile(mFd.getFileDescriptor());
+ }
+
+ /**
* Create and return a new auto-close input stream for this asset. This
* will either return a full asset {@link AutoCloseInputStream}, or
* an underlying {@link ParcelFileDescriptor.AutoCloseInputStream
@@ -132,6 +142,12 @@
* should only call this once for a particular asset.
*/
public FileInputStream createInputStream() throws IOException {
+ if (isMemoryFile()) {
+ if (mLength > Integer.MAX_VALUE) {
+ throw new IOException("File length too large for a memory file: " + mLength);
+ }
+ return new AutoCloseMemoryFileInputStream(mFd, (int)mLength);
+ }
if (mLength < 0) {
return new ParcelFileDescriptor.AutoCloseInputStream(mFd);
}
@@ -262,6 +278,66 @@
}
/**
+ * An input stream that reads from a MemoryFile and closes it when the stream is closed.
+ * This extends FileInputStream just because {@link #createInputStream} returns
+ * a FileInputStream. All the FileInputStream methods are
+ * overridden to use the MemoryFile instead.
+ */
+ private static class AutoCloseMemoryFileInputStream extends FileInputStream {
+ private ParcelFileDescriptor mParcelFd;
+ private MemoryFile mFile;
+ private InputStream mStream;
+
+ public AutoCloseMemoryFileInputStream(ParcelFileDescriptor fd, int length)
+ throws IOException {
+ super(fd.getFileDescriptor());
+ mParcelFd = fd;
+ mFile = new MemoryFile(fd.getFileDescriptor(), length, "r");
+ mStream = mFile.getInputStream();
+ }
+
+ @Override
+ public int available() throws IOException {
+ return mStream.available();
+ }
+
+ @Override
+ public void close() throws IOException {
+ mParcelFd.close(); // must close ParcelFileDescriptor, not just the file descriptor,
+ // since it could be a subclass of ParcelFileDescriptor.
+ // E.g. ContentResolver.ParcelFileDescriptorInner.close() releases
+ // a content provider
+ mFile.close(); // to unmap the memory file from the address space.
+ mStream.close(); // doesn't actually do anything
+ }
+
+ @Override
+ public FileChannel getChannel() {
+ return null;
+ }
+
+ @Override
+ public int read() throws IOException {
+ return mStream.read();
+ }
+
+ @Override
+ public int read(byte[] buffer, int offset, int count) throws IOException {
+ return mStream.read(buffer, offset, count);
+ }
+
+ @Override
+ public int read(byte[] buffer) throws IOException {
+ return mStream.read(buffer);
+ }
+
+ @Override
+ public long skip(long count) throws IOException {
+ return mStream.skip(count);
+ }
+ }
+
+ /**
* An OutputStream you can create on a ParcelFileDescriptor, which will
* take care of calling {@link ParcelFileDescriptor#close
* ParcelFileDescritor.close()} for you when the stream is closed.
@@ -345,4 +421,16 @@
return new AssetFileDescriptor[size];
}
};
+
+ /**
+ * Creates an AssetFileDescriptor from a memory file.
+ *
+ * @hide
+ */
+ public static AssetFileDescriptor fromMemoryFile(MemoryFile memoryFile)
+ throws IOException {
+ ParcelFileDescriptor fd = memoryFile.getParcelFileDescriptor();
+ return new AssetFileDescriptor(fd, 0, memoryFile.length());
+ }
+
}
diff --git a/core/java/android/gesture/GestureOverlayView.java b/core/java/android/gesture/GestureOverlayView.java
index 227cf3d..6f2c2a7 100755
--- a/core/java/android/gesture/GestureOverlayView.java
+++ b/core/java/android/gesture/GestureOverlayView.java
@@ -106,6 +106,9 @@
// TODO: Make this a list of WeakReferences
private final ArrayList<OnGesturePerformedListener> mOnGesturePerformedListeners =
new ArrayList<OnGesturePerformedListener>();
+ // TODO: Make this a list of WeakReferences
+ private final ArrayList<OnGesturingListener> mOnGesturingListeners =
+ new ArrayList<OnGesturingListener>();
private boolean mHandleGestureActions;
@@ -319,6 +322,18 @@
mHandleGestureActions = false;
}
+ public void addOnGesturingListener(OnGesturingListener listener) {
+ mOnGesturingListeners.add(listener);
+ }
+
+ public void removeOnGesturingListener(OnGesturingListener listener) {
+ mOnGesturingListeners.remove(listener);
+ }
+
+ public void removeAllOnGesturingListeners() {
+ mOnGesturingListeners.clear();
+ }
+
public boolean isGesturing() {
return mIsGesturing;
}
@@ -401,7 +416,7 @@
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
- final int count = listeners.size();
+ int count = listeners.size();
for (int i = 0; i < count; i++) {
listeners.get(i).onGestureCancelled(this, event);
}
@@ -411,6 +426,12 @@
clear(false);
mIsGesturing = false;
mStrokeBuffer.clear();
+
+ final ArrayList<OnGesturingListener> otherListeners = mOnGesturingListeners;
+ count = otherListeners.size();
+ for (int i = 0; i < count; i++) {
+ otherListeners.get(i).onGesturingEnded(this);
+ }
}
@Override
@@ -577,6 +598,12 @@
mIsGesturing = true;
setCurrentColor(mCertainGestureColor);
+
+ final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;
+ int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGesturingStarted(this);
+ }
}
}
}
@@ -621,6 +648,12 @@
mStrokeBuffer.clear();
mIsGesturing = false;
+
+ final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;
+ int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGesturingEnded(this);
+ }
}
private void cancelGesture(MotionEvent event) {
@@ -635,12 +668,10 @@
}
private void fireOnGesturePerformed() {
- final ArrayList<OnGesturePerformedListener> actionListeners =
- mOnGesturePerformedListeners;
+ final ArrayList<OnGesturePerformedListener> actionListeners = mOnGesturePerformedListeners;
final int count = actionListeners.size();
for (int i = 0; i < count; i++) {
- actionListeners.get(i).onGesturePerformed(GestureOverlayView.this,
- mCurrentGesture);
+ actionListeners.get(i).onGesturePerformed(GestureOverlayView.this, mCurrentGesture);
}
}
@@ -683,6 +714,12 @@
}
}
+ public static interface OnGesturingListener {
+ void onGesturingStarted(GestureOverlayView overlay);
+
+ void onGesturingEnded(GestureOverlayView overlay);
+ }
+
public static interface OnGestureListener {
void onGestureStarted(GestureOverlayView overlay, MotionEvent event);
diff --git a/core/java/android/os/MemoryFile.java b/core/java/android/os/MemoryFile.java
index 65e83c7..c14925c 100644
--- a/core/java/android/os/MemoryFile.java
+++ b/core/java/android/os/MemoryFile.java
@@ -37,9 +37,14 @@
{
private static String TAG = "MemoryFile";
+ // mmap(2) protection flags from <sys/mman.h>
+ private static final int PROT_READ = 0x1;
+ private static final int PROT_WRITE = 0x2;
+
private static native FileDescriptor native_open(String name, int length) throws IOException;
// returns memory address for ashmem region
- private static native int native_mmap(FileDescriptor fd, int length) throws IOException;
+ private static native int native_mmap(FileDescriptor fd, int length, int mode)
+ throws IOException;
private static native void native_munmap(int addr, int length) throws IOException;
private static native void native_close(FileDescriptor fd);
private static native int native_read(FileDescriptor fd, int address, byte[] buffer,
@@ -47,14 +52,16 @@
private static native void native_write(FileDescriptor fd, int address, byte[] buffer,
int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException;
private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException;
+ private static native boolean native_is_ashmem_region(FileDescriptor fd) throws IOException;
private FileDescriptor mFD; // ashmem file descriptor
private int mAddress; // address of ashmem memory
private int mLength; // total length of our ashmem region
private boolean mAllowPurging = false; // true if our ashmem region is unpinned
+ private final boolean mOwnsRegion; // false if this is a ref to an existing ashmem region
/**
- * MemoryFile constructor.
+ * Allocates a new ashmem region. The region is initially not purgable.
*
* @param name optional name for the file (can be null).
* @param length of the memory file in bytes.
@@ -63,11 +70,43 @@
public MemoryFile(String name, int length) throws IOException {
mLength = length;
mFD = native_open(name, length);
- mAddress = native_mmap(mFD, length);
+ mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE);
+ mOwnsRegion = true;
}
/**
- * Closes and releases all resources for the memory file.
+ * Creates a reference to an existing memory file. Changes to the original file
+ * will be available through this reference.
+ * Calls to {@link #allowPurging(boolean)} on the returned MemoryFile will fail.
+ *
+ * @param fd File descriptor for an existing memory file, as returned by
+ * {@link #getFileDescriptor()}. This file descriptor will be closed
+ * by {@link #close()}.
+ * @param length Length of the memory file in bytes.
+ * @param mode File mode. Currently only "r" for read-only access is supported.
+ * @throws NullPointerException if <code>fd</code> is null.
+ * @throws IOException If <code>fd</code> does not refer to an existing memory file,
+ * or if the file mode of the existing memory file is more restrictive
+ * than <code>mode</code>.
+ *
+ * @hide
+ */
+ public MemoryFile(FileDescriptor fd, int length, String mode) throws IOException {
+ if (fd == null) {
+ throw new NullPointerException("File descriptor is null.");
+ }
+ if (!isMemoryFile(fd)) {
+ throw new IllegalArgumentException("Not a memory file.");
+ }
+ mLength = length;
+ mFD = fd;
+ mAddress = native_mmap(mFD, length, modeToProt(mode));
+ mOwnsRegion = false;
+ }
+
+ /**
+ * Closes the memory file. If there are no other open references to the memory
+ * file, it will be deleted.
*/
public void close() {
deactivate();
@@ -76,7 +115,14 @@
}
}
- private void deactivate() {
+ /**
+ * Unmaps the memory file from the process's memory space, but does not close it.
+ * After this method has been called, read and write operations through this object
+ * will fail, but {@link #getFileDescriptor()} will still return a valid file descriptor.
+ *
+ * @hide
+ */
+ public void deactivate() {
if (!isDeactivated()) {
try {
native_munmap(mAddress, mLength);
@@ -135,6 +181,9 @@
* @return previous value of allowPurging
*/
synchronized public boolean allowPurging(boolean allowPurging) throws IOException {
+ if (!mOwnsRegion) {
+ throw new IOException("Only the owner can make ashmem regions purgable.");
+ }
boolean oldValue = mAllowPurging;
if (oldValue != allowPurging) {
native_pin(mFD, !allowPurging);
@@ -210,6 +259,64 @@
native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);
}
+ /**
+ * Gets a ParcelFileDescriptor for the memory file. See {@link #getFileDescriptor()}
+ * for caveats. This must be here to allow classes outside <code>android.os</code< to
+ * make ParcelFileDescriptors from MemoryFiles, as
+ * {@link ParcelFileDescriptor#ParcelFileDescriptor(FileDescriptor)} is package private.
+ *
+ *
+ * @return The file descriptor owned by this memory file object.
+ * The file descriptor is not duplicated.
+ * @throws IOException If the memory file has been closed.
+ *
+ * @hide
+ */
+ public ParcelFileDescriptor getParcelFileDescriptor() throws IOException {
+ return new ParcelFileDescriptor(getFileDescriptor());
+ }
+
+ /**
+ * Gets a FileDescriptor for the memory file. Note that this file descriptor
+ * is only safe to pass to {@link #MemoryFile(FileDescriptor,int)}). It
+ * should not be used with file descriptor operations that expect a file descriptor
+ * for a normal file.
+ *
+ * The returned file descriptor is not duplicated.
+ *
+ * @throws IOException If the memory file has been closed.
+ *
+ * @hide
+ */
+ public FileDescriptor getFileDescriptor() throws IOException {
+ return mFD;
+ }
+
+ /**
+ * Checks whether the given file descriptor refers to a memory file.
+ *
+ * @throws IOException If <code>fd</code> is not a valid file descriptor.
+ *
+ * @hide
+ */
+ public static boolean isMemoryFile(FileDescriptor fd) throws IOException {
+ return native_is_ashmem_region(fd);
+ }
+
+ /**
+ * Converts a file mode string to a <code>prot</code> value as expected by
+ * native_mmap().
+ *
+ * @throws IllegalArgumentException if the file mode is invalid.
+ */
+ private static int modeToProt(String mode) {
+ if ("r".equals(mode)) {
+ return PROT_READ;
+ } else {
+ throw new IllegalArgumentException("Unsupported file mode: '" + mode + "'");
+ }
+ }
+
private class MemoryInputStream extends InputStream {
private int mMark = 0;
@@ -246,13 +353,22 @@
}
int result = read(mSingleByte, 0, 1);
if (result != 1) {
- throw new IOException("read() failed");
+ return -1;
}
return mSingleByte[0];
}
@Override
public int read(byte buffer[], int offset, int count) throws IOException {
+ if (offset < 0 || count < 0 || offset + count > buffer.length) {
+ // readBytes() also does this check, but we need to do it before
+ // changing count.
+ throw new IndexOutOfBoundsException();
+ }
+ count = Math.min(count, available());
+ if (count < 1) {
+ return -1;
+ }
int result = readBytes(buffer, mOffset, offset, count);
if (result > 0) {
mOffset += result;
diff --git a/core/jni/android_os_MemoryFile.cpp b/core/jni/android_os_MemoryFile.cpp
index 6c16150..8643393 100644
--- a/core/jni/android_os_MemoryFile.cpp
+++ b/core/jni/android_os_MemoryFile.cpp
@@ -39,17 +39,17 @@
if (result < 0) {
jniThrowException(env, "java/io/IOException", "ashmem_create_region failed");
- return NULL;
+ return NULL;
}
return jniCreateFileDescriptor(env, result);
}
static jint android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobject fileDescriptor,
- jint length)
+ jint length, jint prot)
{
int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
- jint result = (jint)mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ jint result = (jint)mmap(NULL, length, prot, MAP_SHARED, fd, 0);
if (!result)
jniThrowException(env, "java/io/IOException", "mmap failed");
return result;
@@ -118,14 +118,36 @@
}
}
+static jboolean android_os_MemoryFile_is_ashmem_region(JNIEnv* env, jobject clazz,
+ jobject fileDescriptor) {
+ int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+ // Use ASHMEM_GET_SIZE to find out if the fd refers to an ashmem region.
+ // ASHMEM_GET_SIZE should succeed for all ashmem regions, and the kernel
+ // should return ENOTTY for all other valid file descriptors
+ int result = ashmem_get_size_region(fd);
+ if (result < 0) {
+ if (errno == ENOTTY) {
+ // ENOTTY means that the ioctl does not apply to this object,
+ // i.e., it is not an ashmem region.
+ return JNI_FALSE;
+ }
+ // Some other error, throw exception
+ jniThrowIOException(env, errno);
+ return JNI_FALSE;
+ }
+ return JNI_TRUE;
+}
+
static const JNINativeMethod methods[] = {
{"native_open", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_MemoryFile_open},
- {"native_mmap", "(Ljava/io/FileDescriptor;I)I", (void*)android_os_MemoryFile_mmap},
+ {"native_mmap", "(Ljava/io/FileDescriptor;II)I", (void*)android_os_MemoryFile_mmap},
{"native_munmap", "(II)V", (void*)android_os_MemoryFile_munmap},
{"native_close", "(Ljava/io/FileDescriptor;)V", (void*)android_os_MemoryFile_close},
{"native_read", "(Ljava/io/FileDescriptor;I[BIIIZ)I", (void*)android_os_MemoryFile_read},
{"native_write", "(Ljava/io/FileDescriptor;I[BIIIZ)V", (void*)android_os_MemoryFile_write},
{"native_pin", "(Ljava/io/FileDescriptor;Z)V", (void*)android_os_MemoryFile_pin},
+ {"native_is_ashmem_region", "(Ljava/io/FileDescriptor;)Z",
+ (void*)android_os_MemoryFile_is_ashmem_region}
};
static const char* const kClassPathName = "android/os/MemoryFile";
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 96369f4..f67f04cf 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -18,7 +18,7 @@
*/
-->
<resources>
- <drawable name="screen_background_light">#ffffffff</drawable>
+ <drawable name="screen_background_light">#fff9f9f9</drawable>
<drawable name="screen_background_dark">#ff1a1a1a</drawable>
<drawable name="status_bar_closed_default_background">#ff000000</drawable>
<drawable name="status_bar_opened_default_background">#ff000000</drawable>
@@ -37,7 +37,7 @@
<color name="dim_foreground_dark_inverse">#323232</color>
<color name="dim_foreground_dark_inverse_disabled">#80323232</color>
<color name="hint_foreground_dark">#808080</color>
- <color name="background_light">#ffffffff</color>
+ <color name="background_light">#fff9f9f9</color>
<color name="bright_foreground_light">#ff000000</color>
<color name="bright_foreground_light_inverse">#ffffffff</color>
<color name="bright_foreground_light_disabled">#80000000</color>
@@ -58,7 +58,7 @@
<drawable name="editbox_dropdown_dark_frame">@drawable/editbox_dropdown_background_dark</drawable>
<drawable name="editbox_dropdown_light_frame">@drawable/editbox_dropdown_background</drawable>
- <drawable name="input_method_fullscreen_background">#ffffffff</drawable>
+ <drawable name="input_method_fullscreen_background">#fff9f9f9</drawable>
<!-- For date picker widget -->
<drawable name="selected_day_background">#ff0092f4</drawable>
diff --git a/opengl/tests/lighting1709/src/com/android/lightingtest/ClearActivity.java b/opengl/tests/lighting1709/src/com/android/lightingtest/ClearActivity.java
index 3dc31cc..3ae8c5c 100644
--- a/opengl/tests/lighting1709/src/com/android/lightingtest/ClearActivity.java
+++ b/opengl/tests/lighting1709/src/com/android/lightingtest/ClearActivity.java
@@ -34,8 +34,6 @@
public class ClearActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
- instance = counter++;
- Log.e("ClearActivity", ":::::: onCreate: instance" + instance + " is created");
super.onCreate(savedInstanceState);
mGLView = new ClearGLSurfaceView(this);
setContentView(mGLView);
@@ -43,96 +41,37 @@
@Override
protected void onPause() {
- Log.e("ClearActivity", ":::::: instance" + instance + " onPause: is called");
super.onPause();
mGLView.onPause();
}
@Override
protected void onResume() {
- Log.e("ClearActivity", ":::::: instance" + instance + " onResume: is called");
super.onResume();
mGLView.onResume();
}
-
- @Override
- protected void onStop() {
- Log.e("ClearActivity", ":::::: instance" + instance + " onStop: is called");
- super.onStop();
- }
-
- @Override
- protected void onDestroy() {
- Log.e("ClearActivity", ":::::: instance" + instance + " onDestroy: is called");
- super.onDestroy();
- }
-
private GLSurfaceView mGLView;
-
- private static int counter = 0;
- private int instance;
}
class ClearGLSurfaceView extends GLSurfaceView {
public ClearGLSurfaceView(Context context) {
super(context);
- instance = counter++;
- Log.e("ClearGLSurfaceView", ":::::: instance" + instance + " is created");
mRenderer = new ClearRenderer();
setRenderer(mRenderer);
}
- public boolean onTouchEvent(final MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- case MotionEvent.ACTION_MOVE: {// falling through on purpose here
- Log.e("ClearGLSurfaceView", ":::::: instance" + instance + " onTouchEvent: handling down or move action");
- queueEvent(new Runnable(){
- public void run() {
- mRenderer.setColor(event.getX() / getWidth(),
- event.getY() / getHeight(), 1.0f);
- }}
- );
- return true;
- }
- case MotionEvent.ACTION_UP: {
- // launch a second instance of the same activity
- Log.e("ClearGLSurfaceView", ":::::: instance" + instance + " onTouchEvent: handling up action");
- // Intent intent = new Intent();
- // intent.setClass(getContext(), ClearActivity.class);
- // getContext().startActivity(intent);
- }
-
- }
- return true;
- }
-
- @Override
- protected void onDetachedFromWindow() {
- Log.e("ClearGLSurfaceView", ":::::: instance" + instance + " onDetachedFromWindow: is called");
- super.onDetachedFromWindow();
- }
-
ClearRenderer mRenderer;
-
- private static int counter = 0;
- private int instance;
}
class ClearRenderer implements GLSurfaceView.Renderer {
public ClearRenderer() {
- instance = counter++;
- Log.e("ClearRenderer", ":::::: instance" + instance + " is created");
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// Do nothing special.
- Log.e("ClearRenderer", ":::::: instance" + instance + " onSurfaceCreated: is called");
}
public void onSurfaceChanged(GL10 gl, int w, int h) {
- Log.e("ClearRenderer", ":::::: instance" + instance + " onSurfaceChanged: is called");
-
// Compute the projection matrix
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
@@ -153,129 +92,83 @@
}
public void onDrawFrame(GL10 gl) {
- // gl.glClearColor(mRed, mGreen, mBlue, 1.0f);
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
- float lightOff[] = {0.0f, 0.0f, 0.0f, 1.0f};
- float lightAmbient[] = {5.0f, 0.0f, 0.0f, 1.0f};
- float lightDiffuse[] = {0.0f, 2.0f, 0.0f, 0.0f};
- float lightPosAmbient[] = {0.0f, 0.0f, 0.0f, 1.0f};
- float lightPosSpot[] = {0.0f, 0.0f, -8.0f, 1.0f};
+ final float lightOff[] = {0.0f, 0.0f, 0.0f, 1.0f};
+ final float lightAmbient[] = {5.0f, 0.0f, 0.0f, 1.0f};
+ final float lightDiffuse[] = {0.0f, 2.0f, 0.0f, 0.0f};
+ final float lightPosSpot[] = {0.0f, 0.0f, -8.0f, 1.0f};
+ final float pos[] = {
+ -5.0f, -1.5f, 0.0f,
+ 0.0f, -1.5f, 0.0f,
+ 5.0f, -1.5f, 0.0f,
+ };
- float v[] = new float[9];
+ final float v[] = new float[9];
ByteBuffer vbb = ByteBuffer.allocateDirect(v.length*4);
vbb.order(ByteOrder.nativeOrder());
FloatBuffer vb = vbb.asFloatBuffer();
gl.glDisable(GL10.GL_DITHER);
- gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, lightOff, 0);
- gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, lightOff, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, lightAmbient, 0);
- gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPosAmbient, 0);
+ gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, lightDiffuse, 0);
+ gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, lightOff, 0);
+ gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPosSpot, 0);
gl.glEnable(GL10.GL_LIGHT0);
-
- gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_SPECULAR, lightOff, 0);
- gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_DIFFUSE, lightDiffuse, 0);
- gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_AMBIENT, lightOff, 0);
- gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_POSITION, lightPosSpot, 0);
- gl.glLightf(GL10.GL_LIGHT1, GL10.GL_CONSTANT_ATTENUATION, 1.0f);
- gl.glLightf(GL10.GL_LIGHT1, GL10.GL_LINEAR_ATTENUATION, 0.0f);
- gl.glLightf(GL10.GL_LIGHT1, GL10.GL_QUADRATIC_ATTENUATION, 0.022f);
- gl.glEnable(GL10.GL_LIGHT1);
-
+
gl.glEnable(GL10.GL_LIGHTING);
- // draw upper left triangle
- gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
- v[0] = -6f; v[1] = 0.5f; v[2] = -10f;
- v[3] = -5f; v[4] = 2.5f; v[5] = -10f;
- v[6] = -4f; v[7] = 0.5f; v[8] = -10f;
- vb.put(v).position(0);
- gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb);
- gl.glNormal3f(0, 0, 1);
- gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3);
- // draw upper middle triangle
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
- v[0] = -1f; v[1] = 0.5f; v[2] = -10f;
- v[3] = 0f; v[4] = 2.5f; v[5] = -10f;
- v[6] = 1f; v[7] = 0.5f; v[8] = -10f;
- vb.put(v).position(0);
- gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb);
gl.glNormal3f(0, 0, 1);
- gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3);
+
- // draw upper right triangle
- gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
- v[0] = 4f; v[1] = 0.5f; v[2] = -10f;
- v[3] = 5f; v[4] = 2.5f; v[5] = -10f;
- v[6] = 6f; v[7] = 0.5f; v[8] = -10f;
+ // draw first 3 triangles, without using transforms
+ for (int i=0 ; i<3 ; i++) {
+ v[0] = -1; v[1] =-1; v[2] = -10;
+ v[3] = 0; v[4] = 1; v[5] = -10;
+ v[6] = 1; v[7] =-1; v[8] = -10;
+ for (int j=0 ; j<3 ; j++) {
+ v[j*3+0] -= pos[i*3+0];
+ v[j*3+1] -= pos[i*3+1];
+ v[j*3+2] -= pos[i*3+2];
+ }
+ vb.put(v).position(0);
+ gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb);
+ gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3);
+ }
+
+ // draw the 2nd batch this time with transforms
+ v[0] = -1; v[1] =-1; v[2] = -10;
+ v[3] = 0; v[4] = 1; v[5] = -10;
+ v[6] = 1; v[7] =-1; v[8] = -10;
vb.put(v).position(0);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb);
- gl.glNormal3f(0, 0, 1);
- gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3);
// draw lower left triangle
gl.glPushMatrix();
- gl.glTranslatef(-5.0f, -1.5f, 0.0f);
- gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
- v[0] = -1; v[1] = -1; v[2] = -10;
- v[3] = 0; v[4] = 1; v[5] = -10;
- v[6] = 1; v[7] = -1; v[8] = -10;
- vb.put(v).position(0);
- gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb);
- gl.glNormal3f(0, 0, 1);
+ gl.glTranslatef(pos[0], pos[1], pos[2]);
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3);
gl.glPopMatrix();
// draw lower middle triangle
gl.glPushMatrix();
- gl.glTranslatef(0.0f, -1.5f, 0.0f);
- gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
- v[0] = -1; v[1] = -1; v[2] = -10;
- v[3] = 0; v[4] = 1; v[5] = -10;
- v[6] = 1; v[7] = -1; v[8] = -10;
- vb.put(v).position(0);
- gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb);
- gl.glNormal3f(0, 0, 1);
+ gl.glTranslatef(pos[3], pos[4], pos[5]);
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3);
gl.glPopMatrix();
// draw lower right triangle
gl.glPushMatrix();
- gl.glTranslatef(5.0f, -1.5f, 0.0f);
- gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
- v[0] = -1; v[1] = -1; v[2] = -10;
- v[3] = 0; v[4] = 1; v[5] = -10;
- v[6] = 1; v[7] = -1; v[8] = -10;
- vb.put(v).position(0);
- gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb);
- gl.glNormal3f(0, 0, 1);
+ gl.glTranslatef(pos[6], pos[7], pos[8]);
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3);
gl.glPopMatrix();
-
}
public int[] getConfigSpec() {
- Log.e("ClearRenderer", ":::::: instance" + instance + " getConfigSpec: is called");
int[] configSpec = { EGL10.EGL_DEPTH_SIZE, 16, EGL10.EGL_NONE };
return configSpec;
}
-
- public void setColor(float r, float g, float b) {
- Log.e("ClearRenderer", ":::::: instance" + instance + " setColor: is called");
- mRed = r;
- mGreen = g;
- mBlue = b;
- }
-
- private float mRed;
- private float mGreen;
- private float mBlue;
-
- private static int counter = 0;
- private int instance;
}
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index c9bdd3c..8da40ac 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -19,6 +19,7 @@
import com.android.internal.app.ResolverActivity;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
+import com.android.server.PackageManagerService.PreferredActivity;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -4507,6 +4508,42 @@
}
}
+ public void replacePreferredActivity(IntentFilter filter, int match,
+ ComponentName[] set, ComponentName activity) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
+ if (filter.countActions() != 1) {
+ throw new IllegalArgumentException(
+ "replacePreferredActivity expects filter to have only 1 action.");
+ }
+ if (filter.countCategories() != 1) {
+ throw new IllegalArgumentException(
+ "replacePreferredActivity expects filter to have only 1 category.");
+ }
+ if (filter.countDataAuthorities() != 0
+ || filter.countDataPaths() != 0
+ || filter.countDataSchemes() != 0
+ || filter.countDataTypes() != 0) {
+ throw new IllegalArgumentException(
+ "replacePreferredActivity expects filter to have no data authorities, " +
+ "paths, schemes or types.");
+ }
+ synchronized (mPackages) {
+ Iterator<PreferredActivity> it = mSettings.mPreferredActivities.filterIterator();
+ String action = filter.getAction(0);
+ String category = filter.getCategory(0);
+ while (it.hasNext()) {
+ PreferredActivity pa = it.next();
+ if (pa.getAction(0).equals(action) && pa.getCategory(0).equals(category)) {
+ it.remove();
+ Log.i(TAG, "Removed preferred activity " + pa.mActivity + ":");
+ filter.dump(new LogPrinter(Log.INFO, TAG), " ");
+ }
+ }
+ addPreferredActivity(filter, match, set, activity);
+ }
+ }
+
public void clearPackagePreferredActivities(String packageName) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
diff --git a/telephony/java/com/android/internal/telephony/RIL.java b/telephony/java/com/android/internal/telephony/RIL.java
index f206d90..0cdeeff 100644
--- a/telephony/java/com/android/internal/telephony/RIL.java
+++ b/telephony/java/com/android/internal/telephony/RIL.java
@@ -165,7 +165,7 @@
}
void
- onError(int error) {
+ onError(int error, Object ret) {
CommandException ex;
ex = CommandException.fromRilErrno(error);
@@ -175,7 +175,7 @@
+ " error: " + ex);
if (mResult != null) {
- AsyncResult.forMessage(mResult, null, ex);
+ AsyncResult.forMessage(mResult, ret, ex);
mResult.sendToTarget();
}
@@ -290,7 +290,7 @@
s = mSocket;
if (s == null) {
- rr.onError(RADIO_NOT_AVAILABLE);
+ rr.onError(RADIO_NOT_AVAILABLE, null);
rr.release();
mRequestMessagesPending--;
alreadySubtracted = true;
@@ -331,7 +331,7 @@
// make sure this request has not already been handled,
// eg, if RILReceiver cleared the list.
if (req != null || !alreadySubtracted) {
- rr.onError(RADIO_NOT_AVAILABLE);
+ rr.onError(RADIO_NOT_AVAILABLE, null);
rr.release();
}
} catch (RuntimeException exc) {
@@ -340,7 +340,7 @@
// make sure this request has not already been handled,
// eg, if RILReceiver cleared the list.
if (req != null || !alreadySubtracted) {
- rr.onError(GENERIC_FAILURE);
+ rr.onError(GENERIC_FAILURE, null);
rr.release();
}
}
@@ -545,7 +545,7 @@
synchronized (mRequestsList) {
for (int i = 0, sz = mRequestsList.size() ; i < sz ; i++) {
RILRequest rr = mRequestsList.get(i);
- rr.onError(RADIO_NOT_AVAILABLE);
+ rr.onError(RADIO_NOT_AVAILABLE, null);
rr.release();
}
@@ -1986,20 +1986,16 @@
return;
}
- if (error != 0) {
- rr.onError(error);
- rr.release();
- return;
- }
+ Object ret = null;
- Object ret;
-
- try {switch (rr.mRequest) {
-/*
+ if (error == 0 || p.dataAvail() > 0) {
+ // either command succeeds or command fails but with data payload
+ try {switch (rr.mRequest) {
+ /*
cat libs/telephony/ril_commands.h \
| egrep "^ *{RIL_" \
| sed -re 's/\{([^,]+),[^,]+,([^}]+).+/case \1: ret = \2(p); break;/'
-*/
+ */
case RIL_REQUEST_GET_SIM_STATUS: ret = responseIccCardStatus(p); break;
case RIL_REQUEST_ENTER_SIM_PIN: ret = responseVoid(p); break;
case RIL_REQUEST_ENTER_SIM_PUK: ret = responseVoid(p); break;
@@ -2104,17 +2100,24 @@
default:
throw new RuntimeException("Unrecognized solicited response: " + rr.mRequest);
//break;
- }} catch (Throwable tr) {
- // Exceptions here usually mean invalid RIL responses
+ }} catch (Throwable tr) {
+ // Exceptions here usually mean invalid RIL responses
- Log.w(LOG_TAG, rr.serialString() + "< "
- + requestToString(rr.mRequest)
- + " exception, possible invalid RIL response", tr);
+ Log.w(LOG_TAG, rr.serialString() + "< "
+ + requestToString(rr.mRequest)
+ + " exception, possible invalid RIL response", tr);
- if (rr.mResult != null) {
- AsyncResult.forMessage(rr.mResult, null, tr);
- rr.mResult.sendToTarget();
+ if (rr.mResult != null) {
+ AsyncResult.forMessage(rr.mResult, null, tr);
+ rr.mResult.sendToTarget();
+ }
+ rr.release();
+ return;
}
+ }
+
+ if (error != 0) {
+ rr.onError(error, ret);
rr.release();
return;
}
diff --git a/test-runner/android/test/mock/MockPackageManager.java b/test-runner/android/test/mock/MockPackageManager.java
index 73ae3b9..6ef5539 100644
--- a/test-runner/android/test/mock/MockPackageManager.java
+++ b/test-runner/android/test/mock/MockPackageManager.java
@@ -392,6 +392,16 @@
throw new UnsupportedOperationException();
}
+ /**
+ * @hide - to match hiding in superclass
+ */
+ @Override
+ public void replacePreferredActivity(IntentFilter filter,
+ int match, ComponentName[] set, ComponentName activity) {
+ throw new UnsupportedOperationException();
+ }
+
+
@Override
public void clearPackagePreferredActivities(String packageName) {
throw new UnsupportedOperationException();
diff --git a/tests/AndroidTests/AndroidManifest.xml b/tests/AndroidTests/AndroidManifest.xml
index 843d844..fd6e6d8 100644
--- a/tests/AndroidTests/AndroidManifest.xml
+++ b/tests/AndroidTests/AndroidManifest.xml
@@ -206,6 +206,12 @@
<meta-data android:name="com.android.unit_tests.reference" android:resource="@xml/metadata" />
</provider>
+ <!-- Application components used for content tests -->
+ <provider android:name=".content.MemoryFileProvider"
+ android:authorities="com.android.unit_tests.content.MemoryFileProvider"
+ android:process=":MemoryFileProvider">
+ </provider>
+
<!-- Application components used for os tests -->
<service android:name=".os.MessengerService"
diff --git a/tests/AndroidTests/src/com/android/unit_tests/content/MemoryFileProvider.java b/tests/AndroidTests/src/com/android/unit_tests/content/MemoryFileProvider.java
new file mode 100644
index 0000000..b31ce18
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/content/MemoryFileProvider.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.unit_tests.content;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.MemoryFile;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/** Simple test provider that runs in the local process. */
+public class MemoryFileProvider extends ContentProvider {
+ private static final String TAG = "MemoryFileProvider";
+
+ private static final String DATA_FILE = "data.bin";
+
+ // some random data
+ public static final byte[] TEST_BLOB = new byte[] {
+ -12, 127, 0, 3, 1, 2, 3, 4, 5, 6, 1, -128, -1, -54, -65, 35,
+ -53, -96, -74, -74, -55, -43, -69, 3, 52, -58,
+ -121, 127, 87, -73, 16, -13, -103, -65, -128, -36,
+ 107, 24, 118, -17, 97, 97, -88, 19, -94, -54,
+ 53, 43, 44, -27, -124, 28, -74, 26, 35, -36,
+ 16, -124, -31, -31, -128, -79, 108, 116, 43, -17 };
+
+ private SQLiteOpenHelper mOpenHelper;
+
+ private static final int DATA_ID_BLOB = 1;
+ private static final int HUGE = 2;
+ private static final int FILE = 3;
+
+ private static final UriMatcher sURLMatcher = new UriMatcher(
+ UriMatcher.NO_MATCH);
+
+ static {
+ sURLMatcher.addURI("*", "data/#/blob", DATA_ID_BLOB);
+ sURLMatcher.addURI("*", "huge", HUGE);
+ sURLMatcher.addURI("*", "file", FILE);
+ }
+
+ private static class DatabaseHelper extends SQLiteOpenHelper {
+ private static final String DATABASE_NAME = "local.db";
+ private static final int DATABASE_VERSION = 1;
+
+ public DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE data (" +
+ "_id INTEGER PRIMARY KEY," +
+ "_blob TEXT, " +
+ "integer INTEGER);");
+
+ // insert alarms
+ ContentValues values = new ContentValues();
+ values.put("_id", 1);
+ values.put("_blob", TEST_BLOB);
+ values.put("integer", 100);
+ db.insert("data", null, values);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
+ Log.w(TAG, "Upgrading test database from version " +
+ oldVersion + " to " + currentVersion +
+ ", which will destroy all old data");
+ db.execSQL("DROP TABLE IF EXISTS data");
+ onCreate(db);
+ }
+ }
+
+
+ public MemoryFileProvider() {
+ }
+
+ @Override
+ public boolean onCreate() {
+ mOpenHelper = new DatabaseHelper(getContext());
+ try {
+ OutputStream out = getContext().openFileOutput(DATA_FILE, Context.MODE_PRIVATE);
+ out.write(TEST_BLOB);
+ out.close();
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri url, String[] projectionIn, String selection,
+ String[] selectionArgs, String sort) {
+ throw new UnsupportedOperationException("query not supported");
+ }
+
+ @Override
+ public String getType(Uri url) {
+ int match = sURLMatcher.match(url);
+ switch (match) {
+ case DATA_ID_BLOB:
+ return "application/octet-stream";
+ case FILE:
+ return "application/octet-stream";
+ default:
+ throw new IllegalArgumentException("Unknown URL");
+ }
+ }
+
+ @Override
+ public AssetFileDescriptor openAssetFile(Uri url, String mode) throws FileNotFoundException {
+ int match = sURLMatcher.match(url);
+ switch (match) {
+ case DATA_ID_BLOB:
+ String sql = "SELECT _blob FROM data WHERE _id=" + url.getPathSegments().get(1);
+ return getBlobColumnAsAssetFile(url, mode, sql);
+ case HUGE:
+ try {
+ MemoryFile memoryFile = new MemoryFile(null, 5000000);
+ memoryFile.writeBytes(TEST_BLOB, 0, 1000000, TEST_BLOB.length);
+ memoryFile.deactivate();
+ return AssetFileDescriptor.fromMemoryFile(memoryFile);
+ } catch (IOException ex) {
+ throw new FileNotFoundException("Error reading " + url + ":" + ex.toString());
+ }
+ case FILE:
+ File file = getContext().getFileStreamPath(DATA_FILE);
+ ParcelFileDescriptor fd =
+ ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ return new AssetFileDescriptor(fd, 0, AssetFileDescriptor.UNKNOWN_LENGTH);
+ default:
+ throw new FileNotFoundException("No files supported by provider at " + url);
+ }
+ }
+
+ private AssetFileDescriptor getBlobColumnAsAssetFile(Uri url, String mode, String sql)
+ throws FileNotFoundException {
+ if (!"r".equals(mode)) {
+ throw new FileNotFoundException("Mode " + mode + " not supported for " + url);
+ }
+ try {
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ MemoryFile file = simpleQueryForBlobMemoryFile(db, sql);
+ if (file == null) throw new FileNotFoundException("No such entry: " + url);
+ AssetFileDescriptor afd = AssetFileDescriptor.fromMemoryFile(file);
+ file.deactivate();
+ // need to dup and then close? openFileHelper() doesn't do that though
+ return afd;
+ } catch (IOException ex) {
+ throw new FileNotFoundException("Error reading " + url + ":" + ex.toString());
+ }
+ }
+
+ private MemoryFile simpleQueryForBlobMemoryFile(SQLiteDatabase db, String sql) throws IOException {
+ Cursor cursor = db.rawQuery(sql, null);
+ try {
+ if (!cursor.moveToFirst()) {
+ return null;
+ }
+ byte[] bytes = cursor.getBlob(0);
+ MemoryFile file = new MemoryFile(null, bytes.length);
+ file.writeBytes(bytes, 0, 0, bytes.length);
+ return file;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ @Override
+ public int update(Uri url, ContentValues values, String where, String[] whereArgs) {
+ throw new UnsupportedOperationException("update not supported");
+ }
+
+ @Override
+ public Uri insert(Uri url, ContentValues initialValues) {
+ throw new UnsupportedOperationException("insert not supported");
+ }
+
+ @Override
+ public int delete(Uri url, String where, String[] whereArgs) {
+ throw new UnsupportedOperationException("delete not supported");
+ }
+}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/content/MemoryFileProviderTest.java b/tests/AndroidTests/src/com/android/unit_tests/content/MemoryFileProviderTest.java
new file mode 100644
index 0000000..f88a9da
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/content/MemoryFileProviderTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.unit_tests.content;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import java.io.InputStream;
+import java.util.Arrays;
+
+/**
+ * Tests reading a MemoryFile-based AssestFile from a ContentProvider running
+ * in a different process.
+ */
+public class MemoryFileProviderTest extends AndroidTestCase {
+
+ // reads from a cross-process AssetFileDescriptor for a MemoryFile
+ @MediumTest
+ public void testRead() throws Exception {
+ ContentResolver resolver = getContext().getContentResolver();
+ Uri uri = Uri.parse("content://com.android.unit_tests.content.MemoryFileProvider/data/1/blob");
+ byte[] buf = new byte[MemoryFileProvider.TEST_BLOB.length];
+ InputStream in = resolver.openInputStream(uri);
+ assertNotNull(in);
+ int count = in.read(buf);
+ assertEquals(buf.length, count);
+ assertEquals(-1, in.read());
+ in.close();
+ assertTrue(Arrays.equals(MemoryFileProvider.TEST_BLOB, buf));
+ }
+
+ // tests that we don't leak file descriptors or virtual address space
+ @MediumTest
+ public void testClose() throws Exception {
+ ContentResolver resolver = getContext().getContentResolver();
+ // open enough file descriptors that we will crash something if we leak FDs
+ // or address space
+ for (int i = 0; i < 1025; i++) {
+ Uri uri = Uri.parse("content://com.android.unit_tests.content.MemoryFileProvider/huge");
+ InputStream in = resolver.openInputStream(uri);
+ assertNotNull("Failed to open stream number " + i, in);
+ assertEquals(1000000, in.skip(1000000));
+ byte[] buf = new byte[MemoryFileProvider.TEST_BLOB.length];
+ int count = in.read(buf);
+ assertEquals(buf.length, count);
+ assertTrue(Arrays.equals(MemoryFileProvider.TEST_BLOB, buf));
+ in.close();
+ }
+ }
+
+ // tests that we haven't broken AssestFileDescriptors for normal files.
+ @MediumTest
+ public void testFile() throws Exception {
+ ContentResolver resolver = getContext().getContentResolver();
+ Uri uri = Uri.parse("content://com.android.unit_tests.content.MemoryFileProvider/file");
+ byte[] buf = new byte[MemoryFileProvider.TEST_BLOB.length];
+ InputStream in = resolver.openInputStream(uri);
+ assertNotNull(in);
+ int count = in.read(buf);
+ assertEquals(buf.length, count);
+ assertEquals(-1, in.read());
+ in.close();
+ assertTrue(Arrays.equals(MemoryFileProvider.TEST_BLOB, buf));
+ }
+
+}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/os/MemoryFileTest.java b/tests/AndroidTests/src/com/android/unit_tests/os/MemoryFileTest.java
index 5161f7b..18b3d63 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/os/MemoryFileTest.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/os/MemoryFileTest.java
@@ -17,19 +17,21 @@
package com.android.unit_tests.os;
import android.os.MemoryFile;
+import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
-import junit.framework.TestCase;
-
-public class MemoryFileTest extends TestCase {
+public class MemoryFileTest extends AndroidTestCase {
private void compareBuffers(byte[] buffer1, byte[] buffer2, int length) throws Exception {
for (int i = 0; i < length; i++) {
@@ -95,6 +97,74 @@
file.close();
}
+ // Tests for the IndexOutOfBoundsException cases in read().
+
+ private void readIndexOutOfBoundsException(int offset, int count, String msg)
+ throws Exception {
+ MemoryFile file = new MemoryFile("MemoryFileTest", testString.length);
+ try {
+ file.writeBytes(testString, 0, 0, testString.length);
+ InputStream is = file.getInputStream();
+ byte[] buffer = new byte[testString.length + 10];
+ try {
+ is.read(buffer, offset, count);
+ fail(msg);
+ } catch (IndexOutOfBoundsException ex) {
+ // this is what should happen
+ } finally {
+ is.close();
+ }
+ } finally {
+ file.close();
+ }
+ }
+
+ @SmallTest
+ public void testReadNegativeOffset() throws Exception {
+ readIndexOutOfBoundsException(-1, 5,
+ "read() with negative offset should throw IndexOutOfBoundsException");
+ }
+
+ @SmallTest
+ public void testReadNegativeCount() throws Exception {
+ readIndexOutOfBoundsException(5, -1,
+ "read() with negative length should throw IndexOutOfBoundsException");
+ }
+
+ @SmallTest
+ public void testReadOffsetOverflow() throws Exception {
+ readIndexOutOfBoundsException(testString.length + 10, 5,
+ "read() with offset outside buffer should throw IndexOutOfBoundsException");
+ }
+
+ @SmallTest
+ public void testReadOffsetCountOverflow() throws Exception {
+ readIndexOutOfBoundsException(testString.length, 11,
+ "read() with offset + count outside buffer should throw IndexOutOfBoundsException");
+ }
+
+ // Test behavior of read() at end of file
+ @SmallTest
+ public void testReadEOF() throws Exception {
+ MemoryFile file = new MemoryFile("MemoryFileTest", testString.length);
+ try {
+ file.writeBytes(testString, 0, 0, testString.length);
+ InputStream is = file.getInputStream();
+ try {
+ byte[] buffer = new byte[testString.length + 10];
+ // read() with count larger than data should succeed, and return # of bytes read
+ assertEquals(testString.length, is.read(buffer));
+ compareBuffers(testString, buffer, testString.length);
+ // Read at EOF should return -1
+ assertEquals(-1, is.read());
+ } finally {
+ is.close();
+ }
+ } finally {
+ file.close();
+ }
+ }
+
// Tests that close() is idempotent
@SmallTest
public void testCloseClose() throws Exception {
@@ -163,6 +233,51 @@
}
}
+ @SmallTest
+ public void testIsMemoryFile() throws Exception {
+ MemoryFile file = new MemoryFile("MemoryFileTest", 1000000);
+ FileDescriptor fd = file.getFileDescriptor();
+ assertNotNull(fd);
+ assertTrue(fd.valid());
+ assertTrue(MemoryFile.isMemoryFile(fd));
+ file.close();
+
+ assertFalse(MemoryFile.isMemoryFile(FileDescriptor.in));
+ assertFalse(MemoryFile.isMemoryFile(FileDescriptor.out));
+ assertFalse(MemoryFile.isMemoryFile(FileDescriptor.err));
+
+ File tempFile = File.createTempFile("MemoryFileTest",".tmp", getContext().getFilesDir());
+ assertNotNull(file);
+ FileOutputStream out = null;
+ try {
+ out = new FileOutputStream(tempFile);
+ FileDescriptor fileFd = out.getFD();
+ assertNotNull(fileFd);
+ assertFalse(MemoryFile.isMemoryFile(fileFd));
+ } finally {
+ if (out != null) {
+ out.close();
+ }
+ tempFile.delete();
+ }
+ }
+
+ @SmallTest
+ public void testFileDescriptor() throws Exception {
+ MemoryFile file = new MemoryFile("MemoryFileTest", 1000000);
+ MemoryFile ref = new MemoryFile(file.getFileDescriptor(), file.length(), "r");
+ byte[] buffer;
+
+ // write to original, read from reference
+ file.writeBytes(testString, 0, 2000, testString.length);
+ buffer = new byte[testString.length];
+ ref.readBytes(buffer, 2000, 0, testString.length);
+ compareBuffers(testString, buffer, testString.length);
+
+ file.close();
+ ref.close(); // Doesn't actually do anything, since the file descriptor is not dup(2):ed
+ }
+
private static final byte[] testString = new byte[] {
3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3, 2, 3, 8, 4, 6, 2, 6, 4, 3, 3, 8, 3, 2, 7, 9, 5, 0, 2, 8, 8, 4, 1, 9, 7, 1, 6, 9, 3, 9, 9, 3, 7, 5, 1, 0, 5, 8, 2, 0, 9, 7, 4, 9, 4, 4, 5, 9, 2, 3, 0, 7, 8, 1, 6, 4,
0, 6, 2, 8, 6, 2, 0, 8, 9, 9, 8, 6, 2, 8, 0, 3, 4, 8, 2, 5, 3, 4, 2, 1, 1, 7, 0, 6, 7, 9, 8, 2, 1, 4, 8, 0, 8, 6, 5, 1, 3, 2, 8, 2, 3, 0, 6, 6, 4, 7, 0, 9, 3, 8, 4, 4, 6, 0, 9, 5, 5, 0, 5, 8, 2, 2, 3, 1, 7, 2,
diff --git a/tests/DumpRenderTree/assets/run_reliability_tests.py b/tests/DumpRenderTree/assets/run_reliability_tests.py
index c12c783..6aab009 100755
--- a/tests/DumpRenderTree/assets/run_reliability_tests.py
+++ b/tests/DumpRenderTree/assets/run_reliability_tests.py
@@ -75,6 +75,11 @@
else:
timedout_file = options.timeout_file
+ if not options.delay:
+ manual_delay = 0
+ else:
+ manual_delay = options.delay
+
adb_cmd = "adb "
if options.adb_options:
adb_cmd += options.adb_options + " "
@@ -110,8 +115,8 @@
# Call ReliabilityTestsAutoTest#startReliabilityTests
test_cmd = (test_cmd_prefix + " -e class "
"com.android.dumprendertree.ReliabilityTest#"
- "runTest -e timeout %d %s" %
- (timeout_ms, test_cmd_postfix))
+ "runReliabilityTest -e timeout %s -e delay %s %s" %
+ (str(timeout_ms), str(manual_delay), test_cmd_postfix))
adb_output = subprocess.Popen(test_cmd, shell=True,
stdout=subprocess.PIPE,
@@ -125,10 +130,6 @@
crashed_tests.append(crashed_test)
logging.info("Resuming reliability test runner...")
- test_cmd = (test_cmd_prefix + " -e class "
- "com.android.dumprendertree.ReliabilityTest#"
- "runTest -e timeout %d %s" %
- (timeout_ms, test_cmd_postfix))
adb_output = subprocess.Popen(test_cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()[0]
@@ -157,20 +158,23 @@
if "__main__" == __name__:
option_parser = optparse.OptionParser()
- option_parser.add_option("", "--time-out-ms",
+ option_parser.add_option("-t", "--time-out-ms",
default=60000,
help="set the timeout for each test")
- option_parser.add_option("", "--verbose", action="store_true",
+ option_parser.add_option("-v", "--verbose", action="store_true",
default=False,
help="include debug-level logging")
- option_parser.add_option("", "--adb-options",
+ option_parser.add_option("-a", "--adb-options",
default=None,
help="pass options to adb, such as -d -e, etc")
- option_parser.add_option("", "--crash-file",
+ option_parser.add_option("-c", "--crash-file",
default="reliability_crashed_sites.txt",
help="the list of sites that cause browser to crash")
- option_parser.add_option("", "--timeout-file",
+ option_parser.add_option("-f", "--timeout-file",
default="reliability_timedout_sites.txt",
help="the list of sites that timedout during test.")
+ option_parser.add_option("-d", "--delay",
+ default=0,
+ help="add a manual delay between pages (in ms)")
opts, arguments = option_parser.parse_args()
main(opts, arguments)
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoRunner.java b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoRunner.java
index ebdc9c7..57e06a1 100755
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoRunner.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoRunner.java
@@ -16,14 +16,11 @@
package com.android.dumprendertree;
-import junit.framework.TestSuite;
-import com.android.dumprendertree.LayoutTestsAutoTest;
-
+import android.os.Bundle;
import android.test.InstrumentationTestRunner;
import android.test.InstrumentationTestSuite;
-import android.util.Log;
-import android.content.Intent;
-import android.os.Bundle;
+
+import junit.framework.TestSuite;
/**
@@ -61,6 +58,14 @@
}
}
+ String delay_str = (String) icicle.get("delay");
+ if(delay_str != null) {
+ try {
+ this.mDelay = Integer.parseInt(delay_str);
+ } catch (Exception e) {
+ }
+ }
+
String r = (String)icicle.get("rebaseline");
this.mRebaseline = (r != null && r.toLowerCase().equals("true"));
super.onCreate(icicle);
@@ -68,6 +73,7 @@
public String mTestPath = null;
public int mTimeoutInMillis = 0;
+ public int mDelay = 0;
public boolean mRebaseline = false;
}
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java
index 081ddafa..e63aa95 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java
@@ -25,13 +25,12 @@
static final String RELIABILITY_TEST_RUNNER_FILES[] = {
"run_reliability_tests.py"
};
-
+
public ReliabilityTest() {
super(PKG_NAME, ReliabilityTestActivity.class);
}
- @Override
- protected void runTest() throws Throwable {
+ public void runReliabilityTest() throws Throwable {
ReliabilityTestActivity activity = getActivity();
LayoutTestsAutoRunner runner = (LayoutTestsAutoRunner)getInstrumentation();
@@ -52,6 +51,7 @@
Handler handler = null;
boolean timeoutFlag = false;
long start, elapsed;
+
//read from BufferedReader instead of populating a list in advance,
//this will avoid excessive memory usage in case of a large list
while((url = listReader.readLine()) != null) {
@@ -64,7 +64,7 @@
handler = activity.getHandler();
handler.sendMessage(handler.obtainMessage(
ReliabilityTestActivity.MSG_NAVIGATE,
- runner.mTimeoutInMillis, 0, url));
+ runner.mTimeoutInMillis, runner.mDelay, url));
timeoutFlag = activity.waitUntilDone();
elapsed = System.currentTimeMillis() - start;
if(elapsed < 1000) {
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTestActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTestActivity.java
index 75f1400..cbec104 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTestActivity.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTestActivity.java
@@ -39,6 +39,8 @@
private boolean pageDone;
private Object pageDoneLock;
private int pageStartCount;
+ private int manualDelay;
+ private PageDoneRunner pageDoneRunner = new PageDoneRunner();
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -73,6 +75,7 @@
handleTimeout();
return;
case MSG_NAVIGATE:
+ manualDelay = msg.arg2;
navigate((String)msg.obj, msg.arg1);
return;
}
@@ -212,6 +215,12 @@
}
@Override
+ public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
+ result.confirm();
+ return true;
+ }
+
+ @Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue,
JsPromptResult result) {
result.confirm();
@@ -246,11 +255,18 @@
public void run() {
if (initialStartCount == pageStartCount) {
//perform cleanup
- webView.stopLoading();
- Log.v(LOGTAG, "Finishing URL: " + webView.getUrl());
handler.removeMessages(MSG_TIMEOUT);
- setPageDone(true);
+ webView.stopLoading();
+ handler.postDelayed(pageDoneRunner, manualDelay);
}
}
}
+
+ class PageDoneRunner implements Runnable {
+
+ public void run() {
+ Log.v(LOGTAG, "Finishing URL: " + webView.getUrl());
+ setPageDone(true);
+ }
+ }
}
diff --git a/tts/java/android/tts/SynthProxy.java b/tts/java/android/tts/SynthProxy.java
index 4ed9754..e065f40 100755
--- a/tts/java/android/tts/SynthProxy.java
+++ b/tts/java/android/tts/SynthProxy.java
@@ -120,7 +120,7 @@
}
static {
- System.loadLibrary("synthproxy");
+ System.loadLibrary("ttssynthproxy");
}
private final static String TAG = "SynthProxy";
diff --git a/tts/java/android/tts/TtsService.java b/tts/java/android/tts/TtsService.java
index 4b794db..d3171818 100755
--- a/tts/java/android/tts/TtsService.java
+++ b/tts/java/android/tts/TtsService.java
@@ -45,563 +45,216 @@
*/
public class TtsService extends Service implements OnCompletionListener {
- private class SpeechItem {
- public static final int SPEECH = 0;
- public static final int EARCON = 1;
- public static final int SILENCE = 2;
- public String mText = null;
- public ArrayList<String> mParams = null;
- public int mType = SPEECH;
- public long mDuration = 0;
+ private static class SpeechItem {
+ public static final int SPEECH = 0;
+ public static final int EARCON = 1;
+ public static final int SILENCE = 2;
+ public String mText = null;
+ public ArrayList<String> mParams = null;
+ public int mType = SPEECH;
+ public long mDuration = 0;
- public SpeechItem(String text, ArrayList<String> params, int itemType) {
- mText = text;
- mParams = params;
- mType = itemType;
- }
-
- public SpeechItem(long silenceTime) {
- mDuration = silenceTime;
- }
- }
-
- /**
- * Contains the information needed to access a sound resource; the name of
- * the package that contains the resource and the resID of the resource
- * within that package.
- */
- private class SoundResource {
- public String mSourcePackageName = null;
- public int mResId = -1;
- public String mFilename = null;
-
- public SoundResource(String packageName, int id) {
- mSourcePackageName = packageName;
- mResId = id;
- mFilename = null;
- }
-
- public SoundResource(String file) {
- mSourcePackageName = null;
- mResId = -1;
- mFilename = file;
- }
- }
-
- private static final String ACTION = "android.intent.action.USE_TTS";
- private static final String CATEGORY = "android.intent.category.TTS";
- private static final String PKGNAME = "android.tts";
-
- final RemoteCallbackList<ITtsCallback> mCallbacks = new RemoteCallbackList<ITtsCallback>();
-
- private Boolean isSpeaking;
- private ArrayList<SpeechItem> speechQueue;
- private HashMap<String, SoundResource> earcons;
- private HashMap<String, SoundResource> utterances;
- private MediaPlayer player;
- private TtsService self;
-
- private SharedPreferences prefs;
-
- private final ReentrantLock speechQueueLock = new ReentrantLock();
- private final ReentrantLock synthesizerLock = new ReentrantLock();
-
- // TODO support multiple SpeechSynthesis objects
- private SynthProxy nativeSynth;
-
- @Override
- public void onCreate() {
- super.onCreate();
- Log.i("TTS", "TTS starting");
-
-
- // TODO: Make this work when the settings are done in the main Settings
- // app.
- prefs = PreferenceManager.getDefaultSharedPreferences(this);
-
- // TODO: This should be changed to work by requesting the path
- // from the default engine.
- nativeSynth = new SynthProxy(prefs.getString("engine_pref", ""));
-
-
- self = this;
- isSpeaking = false;
-
- earcons = new HashMap<String, SoundResource>();
- utterances = new HashMap<String, SoundResource>();
-
- speechQueue = new ArrayList<SpeechItem>();
- player = null;
-
- setLanguage(prefs.getString("lang_pref", "en-rUS"));
- setSpeechRate(Integer.parseInt(prefs.getString("rate_pref", "140")));
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- // Don't hog the media player
- cleanUpPlayer();
-
- nativeSynth.shutdown();
-
- // Unregister all callbacks.
- mCallbacks.kill();
- }
-
- private void setSpeechRate(int rate) {
- if (prefs.getBoolean("override_pref", false)) {
- // This is set to the default here so that the preview in the prefs
- // activity will show the change without a restart, even if apps are
- // not allowed to change the defaults.
- rate = Integer.parseInt(prefs.getString("rate_pref", "140"));
- }
- nativeSynth.setSpeechRate(rate);
- }
-
- private void setLanguage(String lang) {
- if (prefs.getBoolean("override_pref", false)) {
- // This is set to the default here so that the preview in the prefs
- // activity will show the change without a restart, even if apps are
- // not
- // allowed to change the defaults.
- lang = prefs.getString("lang_pref", "en-rUS");
- }
- nativeSynth.setLanguage(lang);
- }
-
- private void setEngine(String engineName, String[] requestedLanguages,
- int strictness) {
- // TODO: Implement engine selection code here.
- Intent engineIntent = new Intent(
- "android.intent.action.START_TTS_ENGINE");
- if (engineName != null) {
- engineIntent.addCategory("android.intent.action.tts_engine."
- + engineName);
- }
- for (int i = 0; i < requestedLanguages.length; i++) {
- engineIntent.addCategory("android.intent.action.tts_lang."
- + requestedLanguages[i]);
- }
- ResolveInfo[] enginesArray = new ResolveInfo[0];
- PackageManager pm = getPackageManager();
- enginesArray = pm.queryIntentActivities(engineIntent, 0).toArray(
- enginesArray);
- }
-
- private void setEngine(Intent engineIntent) {
- // TODO: Implement engine selection code here.
- }
-
- private int getEngineStatus() {
- // TODO: Proposal - add a sanity check method that
- // TTS engine plugins must implement.
- return 0;
- }
-
- /**
- * Adds a sound resource to the TTS.
- *
- * @param text
- * The text that should be associated with the sound resource
- * @param packageName
- * The name of the package which has the sound resource
- * @param resId
- * The resource ID of the sound within its package
- */
- private void addSpeech(String text, String packageName, int resId) {
- utterances.put(text, new SoundResource(packageName, resId));
- }
-
- /**
- * Adds a sound resource to the TTS.
- *
- * @param text
- * The text that should be associated with the sound resource
- * @param filename
- * The filename of the sound resource. This must be a complete
- * path like: (/sdcard/mysounds/mysoundbite.mp3).
- */
- private void addSpeech(String text, String filename) {
- utterances.put(text, new SoundResource(filename));
- }
-
- /**
- * Adds a sound resource to the TTS as an earcon.
- *
- * @param earcon
- * The text that should be associated with the sound resource
- * @param packageName
- * The name of the package which has the sound resource
- * @param resId
- * The resource ID of the sound within its package
- */
- private void addEarcon(String earcon, String packageName, int resId) {
- earcons.put(earcon, new SoundResource(packageName, resId));
- }
-
- /**
- * Adds a sound resource to the TTS as an earcon.
- *
- * @param earcon
- * The text that should be associated with the sound resource
- * @param filename
- * The filename of the sound resource. This must be a complete
- * path like: (/sdcard/mysounds/mysoundbite.mp3).
- */
- private void addEarcon(String earcon, String filename) {
- earcons.put(earcon, new SoundResource(filename));
- }
-
- /**
- * Speaks the given text using the specified queueing mode and parameters.
- *
- * @param text
- * The text that should be spoken
- * @param queueMode
- * 0 for no queue (interrupts all previous utterances), 1 for
- * queued
- * @param params
- * An ArrayList of parameters. This is not implemented for all
- * engines.
- */
- private void speak(String text, int queueMode, ArrayList<String> params) {
- if (queueMode == 0) {
- stop();
- }
- speechQueue.add(new SpeechItem(text, params, SpeechItem.SPEECH));
- if (!isSpeaking) {
- processSpeechQueue();
- }
- }
-
- /**
- * Plays the earcon using the specified queueing mode and parameters.
- *
- * @param earcon
- * The earcon that should be played
- * @param queueMode
- * 0 for no queue (interrupts all previous utterances), 1 for
- * queued
- * @param params
- * An ArrayList of parameters. This is not implemented for all
- * engines.
- */
- private void playEarcon(String earcon, int queueMode,
- ArrayList<String> params) {
- if (queueMode == 0) {
- stop();
- }
- speechQueue.add(new SpeechItem(earcon, params, SpeechItem.EARCON));
- if (!isSpeaking) {
- processSpeechQueue();
- }
- }
-
- /**
- * Stops all speech output and removes any utterances still in the queue.
- */
- private void stop() {
- Log.i("TTS", "Stopping");
- speechQueue.clear();
-
- nativeSynth.stop();
- isSpeaking = false;
- if (player != null) {
- try {
- player.stop();
- } catch (IllegalStateException e) {
- // Do nothing, the player is already stopped.
- }
- }
- Log.i("TTS", "Stopped");
- }
-
- public void onCompletion(MediaPlayer arg0) {
- processSpeechQueue();
- }
-
- private void playSilence(long duration, int queueMode,
- ArrayList<String> params) {
- if (queueMode == 0) {
- stop();
- }
- speechQueue.add(new SpeechItem(duration));
- if (!isSpeaking) {
- processSpeechQueue();
- }
- }
-
- private void silence(final long duration) {
- class SilenceThread implements Runnable {
- public void run() {
- try {
- Thread.sleep(duration);
- } catch (InterruptedException e) {
- e.printStackTrace();
- } finally {
- processSpeechQueue();
+ public SpeechItem(String text, ArrayList<String> params, int itemType) {
+ mText = text;
+ mParams = params;
+ mType = itemType;
}
- }
- }
- Thread slnc = (new Thread(new SilenceThread()));
- slnc.setPriority(Thread.MIN_PRIORITY);
- slnc.start();
- }
- private void speakInternalOnly(final String text,
- final ArrayList<String> params) {
- class SynthThread implements Runnable {
- public void run() {
- boolean synthAvailable = false;
- try {
- synthAvailable = synthesizerLock.tryLock();
- if (!synthAvailable) {
- Thread.sleep(100);
- Thread synth = (new Thread(new SynthThread()));
- synth.setPriority(Thread.MIN_PRIORITY);
- synth.start();
- return;
- }
- nativeSynth.speak(text);
- } catch (InterruptedException e) {
- e.printStackTrace();
- } finally {
- // This check is needed because finally will always run;
- // even if the
- // method returns somewhere in the try block.
- if (synthAvailable) {
- synthesizerLock.unlock();
- }
+ public SpeechItem(long silenceTime) {
+ mDuration = silenceTime;
}
- }
}
- Thread synth = (new Thread(new SynthThread()));
- synth.setPriority(Thread.MIN_PRIORITY);
- synth.start();
- }
- private SoundResource getSoundResource(SpeechItem speechItem) {
- SoundResource sr = null;
- String text = speechItem.mText;
- if (speechItem.mType == SpeechItem.SILENCE) {
- // Do nothing if this is just silence
- } else if (speechItem.mType == SpeechItem.EARCON) {
- sr = earcons.get(text);
- } else {
- sr = utterances.get(text);
- }
- return sr;
- }
+ /**
+ * Contains the information needed to access a sound resource; the name of
+ * the package that contains the resource and the resID of the resource
+ * within that package.
+ */
+ private static class SoundResource {
+ public String mSourcePackageName = null;
+ public int mResId = -1;
+ public String mFilename = null;
- private void dispatchSpeechCompletedCallbacks(String mark) {
- Log.i("TTS callback", "dispatch started");
- // Broadcast to all clients the new value.
- final int N = mCallbacks.beginBroadcast();
- for (int i = 0; i < N; i++) {
- try {
- mCallbacks.getBroadcastItem(i).markReached(mark);
- } catch (RemoteException e) {
- // The RemoteCallbackList will take care of removing
- // the dead object for us.
- }
- }
- mCallbacks.finishBroadcast();
- Log.i("TTS callback", "dispatch completed to " + N);
- }
-
- private void processSpeechQueue() {
- boolean speechQueueAvailable = false;
- try {
- speechQueueAvailable = speechQueueLock.tryLock();
- if (!speechQueueAvailable) {
- return;
- }
- if (speechQueue.size() < 1) {
- isSpeaking = false;
- // Dispatch a completion here as this is the
- // only place where speech completes normally.
- // Nothing left to say in the queue is a special case
- // that is always a "mark" - associated text is null.
- dispatchSpeechCompletedCallbacks("");
- return;
- }
-
- SpeechItem currentSpeechItem = speechQueue.get(0);
- isSpeaking = true;
- SoundResource sr = getSoundResource(currentSpeechItem);
- // Synth speech as needed - synthesizer should call
- // processSpeechQueue to continue running the queue
- Log.i("TTS processing: ", currentSpeechItem.mText);
- if (sr == null) {
- if (currentSpeechItem.mType == SpeechItem.SPEECH) {
- // TODO: Split text up into smaller chunks before accepting
- // them
- // for processing.
- speakInternalOnly(currentSpeechItem.mText,
- currentSpeechItem.mParams);
- } else {
- // This is either silence or an earcon that was missing
- silence(currentSpeechItem.mDuration);
+ public SoundResource(String packageName, int id) {
+ mSourcePackageName = packageName;
+ mResId = id;
+ mFilename = null;
}
- } else {
+
+ public SoundResource(String file) {
+ mSourcePackageName = null;
+ mResId = -1;
+ mFilename = file;
+ }
+ }
+
+ private static final String ACTION = "android.intent.action.USE_TTS";
+ private static final String CATEGORY = "android.intent.category.TTS";
+ private static final String PKGNAME = "android.tts";
+
+ final RemoteCallbackList<ITtsCallback> mCallbacks = new RemoteCallbackList<ITtsCallback>();
+
+ private Boolean mIsSpeaking;
+ private ArrayList<SpeechItem> mSpeechQueue;
+ private HashMap<String, SoundResource> mEarcons;
+ private HashMap<String, SoundResource> mUtterances;
+ private MediaPlayer mPlayer;
+ private TtsService mSelf;
+
+ private SharedPreferences prefs;
+
+ private final ReentrantLock speechQueueLock = new ReentrantLock();
+ private final ReentrantLock synthesizerLock = new ReentrantLock();
+
+ // TODO support multiple SpeechSynthesis objects
+ private SynthProxy nativeSynth;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.i("TTS", "TTS starting");
+
+
+ // TODO: Make this work when the settings are done in the main Settings
+ // app.
+ prefs = PreferenceManager.getDefaultSharedPreferences(this);
+
+ // TODO: This should be changed to work by requesting the path
+ // from the default engine.
+ nativeSynth = new SynthProxy(prefs.getString("engine_pref", ""));
+
+
+ mSelf = this;
+ mIsSpeaking = false;
+
+ mEarcons = new HashMap<String, SoundResource>();
+ mUtterances = new HashMap<String, SoundResource>();
+
+ mSpeechQueue = new ArrayList<SpeechItem>();
+ mPlayer = null;
+
+ setLanguage(prefs.getString("lang_pref", "en-rUS"));
+ setSpeechRate(Integer.parseInt(prefs.getString("rate_pref", "140")));
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ // Don't hog the media player
cleanUpPlayer();
- if (sr.mSourcePackageName == PKGNAME) {
- // Utterance is part of the TTS library
- player = MediaPlayer.create(this, sr.mResId);
- } else if (sr.mSourcePackageName != null) {
- // Utterance is part of the app calling the library
- Context ctx;
- try {
- ctx = this.createPackageContext(sr.mSourcePackageName,
- 0);
- } catch (NameNotFoundException e) {
- e.printStackTrace();
- speechQueue.remove(0); // Remove it from the queue and
- // move on
- isSpeaking = false;
- return;
- }
- player = MediaPlayer.create(ctx, sr.mResId);
- } else {
- // Utterance is coming from a file
- player = MediaPlayer.create(this, Uri.parse(sr.mFilename));
+
+ nativeSynth.shutdown();
+
+ // Unregister all callbacks.
+ mCallbacks.kill();
+ }
+
+ private void setSpeechRate(int rate) {
+ if (prefs.getBoolean("override_pref", false)) {
+ // This is set to the default here so that the preview in the prefs
+ // activity will show the change without a restart, even if apps are
+ // not allowed to change the defaults.
+ rate = Integer.parseInt(prefs.getString("rate_pref", "140"));
}
+ nativeSynth.setSpeechRate(rate);
+ }
- // Check if Media Server is dead; if it is, clear the queue and
- // give up for now - hopefully, it will recover itself.
- if (player == null) {
- speechQueue.clear();
- isSpeaking = false;
- return;
+ private void setLanguage(String lang) {
+ if (prefs.getBoolean("override_pref", false)) {
+ // This is set to the default here so that the preview in the prefs
+ // activity will show the change without a restart, even if apps are
+ // not
+ // allowed to change the defaults.
+ lang = prefs.getString("lang_pref", "en-rUS");
}
- player.setOnCompletionListener(this);
- try {
- player.start();
- } catch (IllegalStateException e) {
- speechQueue.clear();
- isSpeaking = false;
- cleanUpPlayer();
- return;
+ nativeSynth.setLanguage(lang);
+ }
+
+ private void setEngine(String engineName, String[] requestedLanguages,
+ int strictness) {
+ // TODO: Implement engine selection code here.
+ Intent engineIntent = new Intent(
+ "android.intent.action.START_TTS_ENGINE");
+ if (engineName != null) {
+ engineIntent.addCategory("android.intent.action.tts_engine."
+ + engineName);
}
- }
- if (speechQueue.size() > 0) {
- speechQueue.remove(0);
- }
- } finally {
- // This check is needed because finally will always run; even if the
- // method returns somewhere in the try block.
- if (speechQueueAvailable) {
- speechQueueLock.unlock();
- }
- }
- }
-
- private void cleanUpPlayer() {
- if (player != null) {
- player.release();
- player = null;
- }
- }
-
- /**
- * Synthesizes the given text using the specified queuing mode and
- * parameters.
- *
- * @param text
- * The String of text that should be synthesized
- * @param params
- * An ArrayList of parameters. The first element of this array
- * controls the type of voice to use.
- * @param filename
- * The string that gives the full output filename; it should be
- * something like "/sdcard/myappsounds/mysound.wav".
- * @return A boolean that indicates if the synthesis succeeded
- */
- private boolean synthesizeToFile(String text, ArrayList<String> params,
- String filename, boolean calledFromApi) {
- // Only stop everything if this is a call made by an outside app trying
- // to
- // use the API. Do NOT stop if this is a call from within the service as
- // clearing the speech queue here would be a mistake.
- if (calledFromApi) {
- stop();
- }
- Log.i("TTS", "Synthesizing to " + filename);
- boolean synthAvailable = false;
- try {
- synthAvailable = synthesizerLock.tryLock();
- if (!synthAvailable) {
- return false;
- }
- // Don't allow a filename that is too long
- // TODO use platform constant
- if (filename.length() > 250) {
- return false;
- }
- nativeSynth.synthesizeToFile(text, filename);
- } finally {
- // This check is needed because finally will always run; even if the
- // method returns somewhere in the try block.
- if (synthAvailable) {
- synthesizerLock.unlock();
- }
- }
- Log.i("TTS", "Completed synthesis for " + filename);
- return true;
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- if (ACTION.equals(intent.getAction())) {
- for (String category : intent.getCategories()) {
- if (category.equals(CATEGORY)) {
- return mBinder;
+ for (int i = 0; i < requestedLanguages.length; i++) {
+ engineIntent.addCategory("android.intent.action.tts_lang."
+ + requestedLanguages[i]);
}
- }
- }
- return null;
- }
-
- private final ITts.Stub mBinder = new Stub() {
-
- public void registerCallback(ITtsCallback cb) {
- if (cb != null)
- mCallbacks.register(cb);
+ ResolveInfo[] enginesArray = new ResolveInfo[0];
+ PackageManager pm = getPackageManager();
+ enginesArray = pm.queryIntentActivities(engineIntent, 0).toArray(
+ enginesArray);
}
- public void unregisterCallback(ITtsCallback cb) {
- if (cb != null)
- mCallbacks.unregister(cb);
+ private void setEngine(Intent engineIntent) {
+ // TODO: Implement engine selection code here.
+ }
+
+ private int getEngineStatus() {
+ // TODO: Proposal - add a sanity check method that
+ // TTS engine plugins must implement.
+ return 0;
}
/**
- * Gives a hint about the type of engine that is preferred.
+ * Adds a sound resource to the TTS.
*
- * @param selectedEngine
- * The TTS engine that should be used
+ * @param text
+ * The text that should be associated with the sound resource
+ * @param packageName
+ * The name of the package which has the sound resource
+ * @param resId
+ * The resource ID of the sound within its package
*/
- public void setEngine(String engineName, String[] supportedLanguages,
- int strictness) {
- self.setEngine(engineName, supportedLanguages, strictness);
+ private void addSpeech(String text, String packageName, int resId) {
+ mUtterances.put(text, new SoundResource(packageName, resId));
}
/**
- * Specifies exactly what the engine has to support. Will always be
- * considered "strict"; can be used for implementing
- * optional/experimental features that are not supported by all engines.
+ * Adds a sound resource to the TTS.
*
- * @param engineIntent
- * An intent that specifies exactly what the engine has to
- * support.
+ * @param text
+ * The text that should be associated with the sound resource
+ * @param filename
+ * The filename of the sound resource. This must be a complete
+ * path like: (/sdcard/mysounds/mysoundbite.mp3).
*/
- public void setEngineWithIntent(Intent engineIntent) {
- self.setEngine(engineIntent);
+ private void addSpeech(String text, String filename) {
+ mUtterances.put(text, new SoundResource(filename));
}
/**
- * Speaks the given text using the specified queueing mode and
- * parameters.
+ * Adds a sound resource to the TTS as an earcon.
+ *
+ * @param earcon
+ * The text that should be associated with the sound resource
+ * @param packageName
+ * The name of the package which has the sound resource
+ * @param resId
+ * The resource ID of the sound within its package
+ */
+ private void addEarcon(String earcon, String packageName, int resId) {
+ mEarcons.put(earcon, new SoundResource(packageName, resId));
+ }
+
+ /**
+ * Adds a sound resource to the TTS as an earcon.
+ *
+ * @param earcon
+ * The text that should be associated with the sound resource
+ * @param filename
+ * The filename of the sound resource. This must be a complete
+ * path like: (/sdcard/mysounds/mysoundbite.mp3).
+ */
+ private void addEarcon(String earcon, String filename) {
+ mEarcons.put(earcon, new SoundResource(filename));
+ }
+
+ /**
+ * Speaks the given text using the specified queueing mode and parameters.
*
* @param text
* The text that should be spoken
@@ -609,15 +262,17 @@
* 0 for no queue (interrupts all previous utterances), 1 for
* queued
* @param params
- * An ArrayList of parameters. The first element of this
- * array controls the type of voice to use.
+ * An ArrayList of parameters. This is not implemented for all
+ * engines.
*/
- public void speak(String text, int queueMode, String[] params) {
- ArrayList<String> speakingParams = new ArrayList<String>();
- if (params != null) {
- speakingParams = new ArrayList<String>(Arrays.asList(params));
- }
- self.speak(text, queueMode, speakingParams);
+ private void speak(String text, int queueMode, ArrayList<String> params) {
+ if (queueMode == 0) {
+ stop();
+ }
+ mSpeechQueue.add(new SpeechItem(text, params, SpeechItem.SPEECH));
+ if (!mIsSpeaking) {
+ processSpeechQueue();
+ }
}
/**
@@ -629,155 +284,500 @@
* 0 for no queue (interrupts all previous utterances), 1 for
* queued
* @param params
- * An ArrayList of parameters.
+ * An ArrayList of parameters. This is not implemented for all
+ * engines.
*/
- public void playEarcon(String earcon, int queueMode, String[] params) {
- ArrayList<String> speakingParams = new ArrayList<String>();
- if (params != null) {
- speakingParams = new ArrayList<String>(Arrays.asList(params));
- }
- self.playEarcon(earcon, queueMode, speakingParams);
+ private void playEarcon(String earcon, int queueMode,
+ ArrayList<String> params) {
+ if (queueMode == 0) {
+ stop();
+ }
+ mSpeechQueue.add(new SpeechItem(earcon, params, SpeechItem.EARCON));
+ if (!mIsSpeaking) {
+ processSpeechQueue();
+ }
}
/**
- * Plays the silence using the specified queueing mode and parameters.
- *
- * @param duration
- * The duration of the silence that should be played
- * @param queueMode
- * 0 for no queue (interrupts all previous utterances), 1 for
- * queued
- * @param params
- * An ArrayList of parameters.
+ * Stops all speech output and removes any utterances still in the queue.
*/
- public void playSilence(long duration, int queueMode, String[] params) {
- ArrayList<String> speakingParams = new ArrayList<String>();
- if (params != null) {
- speakingParams = new ArrayList<String>(Arrays.asList(params));
- }
- self.playSilence(duration, queueMode, speakingParams);
+ private void stop() {
+ Log.i("TTS", "Stopping");
+ mSpeechQueue.clear();
+
+ nativeSynth.stop();
+ mIsSpeaking = false;
+ if (mPlayer != null) {
+ try {
+ mPlayer.stop();
+ } catch (IllegalStateException e) {
+ // Do nothing, the player is already stopped.
+ }
+ }
+ Log.i("TTS", "Stopped");
}
+ public void onCompletion(MediaPlayer arg0) {
+ processSpeechQueue();
+ }
- /**
- * Stops all speech output and removes any utterances still in the
- * queue.
- */
- public void stop() {
- self.stop();
+ private void playSilence(long duration, int queueMode,
+ ArrayList<String> params) {
+ if (queueMode == 0) {
+ stop();
+ }
+ mSpeechQueue.add(new SpeechItem(duration));
+ if (!mIsSpeaking) {
+ processSpeechQueue();
+ }
+ }
+
+ private void silence(final long duration) {
+ class SilenceThread implements Runnable {
+ public void run() {
+ try {
+ Thread.sleep(duration);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } finally {
+ processSpeechQueue();
+ }
+ }
+ }
+ Thread slnc = (new Thread(new SilenceThread()));
+ slnc.setPriority(Thread.MIN_PRIORITY);
+ slnc.start();
+ }
+
+ private void speakInternalOnly(final String text,
+ final ArrayList<String> params) {
+ class SynthThread implements Runnable {
+ public void run() {
+ boolean synthAvailable = false;
+ try {
+ synthAvailable = synthesizerLock.tryLock();
+ if (!synthAvailable) {
+ Thread.sleep(100);
+ Thread synth = (new Thread(new SynthThread()));
+ synth.setPriority(Thread.MIN_PRIORITY);
+ synth.start();
+ return;
+ }
+ nativeSynth.speak(text);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } finally {
+ // This check is needed because finally will always run;
+ // even if the
+ // method returns somewhere in the try block.
+ if (synthAvailable) {
+ synthesizerLock.unlock();
+ }
+ }
+ }
+ }
+ Thread synth = (new Thread(new SynthThread()));
+ synth.setPriority(Thread.MIN_PRIORITY);
+ synth.start();
+ }
+
+ private SoundResource getSoundResource(SpeechItem speechItem) {
+ SoundResource sr = null;
+ String text = speechItem.mText;
+ if (speechItem.mType == SpeechItem.SILENCE) {
+ // Do nothing if this is just silence
+ } else if (speechItem.mType == SpeechItem.EARCON) {
+ sr = mEarcons.get(text);
+ } else {
+ sr = mUtterances.get(text);
+ }
+ return sr;
+ }
+
+ private void dispatchSpeechCompletedCallbacks(String mark) {
+ Log.i("TTS callback", "dispatch started");
+ // Broadcast to all clients the new value.
+ final int N = mCallbacks.beginBroadcast();
+ for (int i = 0; i < N; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).markReached(mark);
+ } catch (RemoteException e) {
+ // The RemoteCallbackList will take care of removing
+ // the dead object for us.
+ }
+ }
+ mCallbacks.finishBroadcast();
+ Log.i("TTS callback", "dispatch completed to " + N);
+ }
+
+ private void processSpeechQueue() {
+ boolean speechQueueAvailable = false;
+ try {
+ speechQueueAvailable = speechQueueLock.tryLock();
+ if (!speechQueueAvailable) {
+ return;
+ }
+ if (mSpeechQueue.size() < 1) {
+ mIsSpeaking = false;
+ // Dispatch a completion here as this is the
+ // only place where speech completes normally.
+ // Nothing left to say in the queue is a special case
+ // that is always a "mark" - associated text is null.
+ dispatchSpeechCompletedCallbacks("");
+ return;
+ }
+
+ SpeechItem currentSpeechItem = mSpeechQueue.get(0);
+ mIsSpeaking = true;
+ SoundResource sr = getSoundResource(currentSpeechItem);
+ // Synth speech as needed - synthesizer should call
+ // processSpeechQueue to continue running the queue
+ Log.i("TTS processing: ", currentSpeechItem.mText);
+ if (sr == null) {
+ if (currentSpeechItem.mType == SpeechItem.SPEECH) {
+ // TODO: Split text up into smaller chunks before accepting
+ // them
+ // for processing.
+ speakInternalOnly(currentSpeechItem.mText,
+ currentSpeechItem.mParams);
+ } else {
+ // This is either silence or an earcon that was missing
+ silence(currentSpeechItem.mDuration);
+ }
+ } else {
+ cleanUpPlayer();
+ if (sr.mSourcePackageName == PKGNAME) {
+ // Utterance is part of the TTS library
+ mPlayer = MediaPlayer.create(this, sr.mResId);
+ } else if (sr.mSourcePackageName != null) {
+ // Utterance is part of the app calling the library
+ Context ctx;
+ try {
+ ctx = this.createPackageContext(sr.mSourcePackageName,
+ 0);
+ } catch (NameNotFoundException e) {
+ e.printStackTrace();
+ mSpeechQueue.remove(0); // Remove it from the queue and
+ // move on
+ mIsSpeaking = false;
+ return;
+ }
+ mPlayer = MediaPlayer.create(ctx, sr.mResId);
+ } else {
+ // Utterance is coming from a file
+ mPlayer = MediaPlayer.create(this, Uri.parse(sr.mFilename));
+ }
+
+ // Check if Media Server is dead; if it is, clear the queue and
+ // give up for now - hopefully, it will recover itself.
+ if (mPlayer == null) {
+ mSpeechQueue.clear();
+ mIsSpeaking = false;
+ return;
+ }
+ mPlayer.setOnCompletionListener(this);
+ try {
+ mPlayer.start();
+ } catch (IllegalStateException e) {
+ mSpeechQueue.clear();
+ mIsSpeaking = false;
+ cleanUpPlayer();
+ return;
+ }
+ }
+ if (mSpeechQueue.size() > 0) {
+ mSpeechQueue.remove(0);
+ }
+ } finally {
+ // This check is needed because finally will always run; even if the
+ // method returns somewhere in the try block.
+ if (speechQueueAvailable) {
+ speechQueueLock.unlock();
+ }
+ }
+ }
+
+ private void cleanUpPlayer() {
+ if (mPlayer != null) {
+ mPlayer.release();
+ mPlayer = null;
+ }
}
/**
- * Returns whether or not the TTS is speaking.
- *
- * @return Boolean to indicate whether or not the TTS is speaking
- */
- public boolean isSpeaking() {
- return (self.isSpeaking && (speechQueue.size() < 1));
- }
-
- /**
- * Adds a sound resource to the TTS.
- *
- * @param text
- * The text that should be associated with the sound resource
- * @param packageName
- * The name of the package which has the sound resource
- * @param resId
- * The resource ID of the sound within its package
- */
- public void addSpeech(String text, String packageName, int resId) {
- self.addSpeech(text, packageName, resId);
- }
-
- /**
- * Adds a sound resource to the TTS.
- *
- * @param text
- * The text that should be associated with the sound resource
- * @param filename
- * The filename of the sound resource. This must be a
- * complete path like: (/sdcard/mysounds/mysoundbite.mp3).
- */
- public void addSpeechFile(String text, String filename) {
- self.addSpeech(text, filename);
- }
-
- /**
- * Adds a sound resource to the TTS as an earcon.
- *
- * @param earcon
- * The text that should be associated with the sound resource
- * @param packageName
- * The name of the package which has the sound resource
- * @param resId
- * The resource ID of the sound within its package
- */
- public void addEarcon(String earcon, String packageName, int resId) {
- self.addEarcon(earcon, packageName, resId);
- }
-
- /**
- * Adds a sound resource to the TTS as an earcon.
- *
- * @param earcon
- * The text that should be associated with the sound resource
- * @param filename
- * The filename of the sound resource. This must be a
- * complete path like: (/sdcard/mysounds/mysoundbite.mp3).
- */
- public void addEarconFile(String earcon, String filename) {
- self.addEarcon(earcon, filename);
- }
-
- /**
- * Sets the speech rate for the TTS. Note that this will only have an
- * effect on synthesized speech; it will not affect pre-recorded speech.
- *
- * @param speechRate
- * The speech rate that should be used
- */
- public void setSpeechRate(int speechRate) {
- self.setSpeechRate(speechRate);
- }
-
- // TODO: Fix comment about language
- /**
- * Sets the speech rate for the TTS. Note that this will only have an
- * effect on synthesized speech; it will not affect pre-recorded speech.
- *
- * @param language
- * The language to be used. The languages are specified by
- * their IETF language tags as defined by BCP 47. This is the
- * same standard used for the lang attribute in HTML. See:
- * http://en.wikipedia.org/wiki/IETF_language_tag
- */
- public void setLanguage(String language) {
- self.setLanguage(language);
- }
-
- /**
- * Speaks the given text using the specified queueing mode and
+ * Synthesizes the given text using the specified queuing mode and
* parameters.
*
* @param text
* The String of text that should be synthesized
* @param params
- * An ArrayList of parameters. The first element of this
- * array controls the type of voice to use.
+ * An ArrayList of parameters. The first element of this array
+ * controls the type of voice to use.
* @param filename
- * The string that gives the full output filename; it should
- * be something like "/sdcard/myappsounds/mysound.wav".
+ * The string that gives the full output filename; it should be
+ * something like "/sdcard/myappsounds/mysound.wav".
* @return A boolean that indicates if the synthesis succeeded
*/
- public boolean synthesizeToFile(String text, String[] params,
- String filename) {
- ArrayList<String> speakingParams = new ArrayList<String>();
- if (params != null) {
- speakingParams = new ArrayList<String>(Arrays.asList(params));
- }
- return self.synthesizeToFile(text, speakingParams, filename, true);
+ private boolean synthesizeToFile(String text, ArrayList<String> params,
+ String filename, boolean calledFromApi) {
+ // Only stop everything if this is a call made by an outside app trying
+ // to
+ // use the API. Do NOT stop if this is a call from within the service as
+ // clearing the speech queue here would be a mistake.
+ if (calledFromApi) {
+ stop();
+ }
+ Log.i("TTS", "Synthesizing to " + filename);
+ boolean synthAvailable = false;
+ try {
+ synthAvailable = synthesizerLock.tryLock();
+ if (!synthAvailable) {
+ return false;
+ }
+ // Don't allow a filename that is too long
+ // TODO use platform constant
+ if (filename.length() > 250) {
+ return false;
+ }
+ nativeSynth.synthesizeToFile(text, filename);
+ } finally {
+ // This check is needed because finally will always run; even if the
+ // method returns somewhere in the try block.
+ if (synthAvailable) {
+ synthesizerLock.unlock();
+ }
+ }
+ Log.i("TTS", "Completed synthesis for " + filename);
+ return true;
}
- };
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (ACTION.equals(intent.getAction())) {
+ for (String category : intent.getCategories()) {
+ if (category.equals(CATEGORY)) {
+ return mBinder;
+ }
+ }
+ }
+ return null;
+ }
+
+ private final ITts.Stub mBinder = new Stub() {
+
+ public void registerCallback(ITtsCallback cb) {
+ if (cb != null)
+ mCallbacks.register(cb);
+ }
+
+ public void unregisterCallback(ITtsCallback cb) {
+ if (cb != null)
+ mCallbacks.unregister(cb);
+ }
+
+ /**
+ * Gives a hint about the type of engine that is preferred.
+ *
+ * @param selectedEngine
+ * The TTS engine that should be used
+ */
+ public void setEngine(String engineName, String[] supportedLanguages,
+ int strictness) {
+ mSelf.setEngine(engineName, supportedLanguages, strictness);
+ }
+
+ /**
+ * Specifies exactly what the engine has to support. Will always be
+ * considered "strict"; can be used for implementing
+ * optional/experimental features that are not supported by all engines.
+ *
+ * @param engineIntent
+ * An intent that specifies exactly what the engine has to
+ * support.
+ */
+ public void setEngineWithIntent(Intent engineIntent) {
+ mSelf.setEngine(engineIntent);
+ }
+
+ /**
+ * Speaks the given text using the specified queueing mode and
+ * parameters.
+ *
+ * @param text
+ * The text that should be spoken
+ * @param queueMode
+ * 0 for no queue (interrupts all previous utterances), 1 for
+ * queued
+ * @param params
+ * An ArrayList of parameters. The first element of this
+ * array controls the type of voice to use.
+ */
+ public void speak(String text, int queueMode, String[] params) {
+ ArrayList<String> speakingParams = new ArrayList<String>();
+ if (params != null) {
+ speakingParams = new ArrayList<String>(Arrays.asList(params));
+ }
+ mSelf.speak(text, queueMode, speakingParams);
+ }
+
+ /**
+ * Plays the earcon using the specified queueing mode and parameters.
+ *
+ * @param earcon
+ * The earcon that should be played
+ * @param queueMode
+ * 0 for no queue (interrupts all previous utterances), 1 for
+ * queued
+ * @param params
+ * An ArrayList of parameters.
+ */
+ public void playEarcon(String earcon, int queueMode, String[] params) {
+ ArrayList<String> speakingParams = new ArrayList<String>();
+ if (params != null) {
+ speakingParams = new ArrayList<String>(Arrays.asList(params));
+ }
+ mSelf.playEarcon(earcon, queueMode, speakingParams);
+ }
+
+ /**
+ * Plays the silence using the specified queueing mode and parameters.
+ *
+ * @param duration
+ * The duration of the silence that should be played
+ * @param queueMode
+ * 0 for no queue (interrupts all previous utterances), 1 for
+ * queued
+ * @param params
+ * An ArrayList of parameters.
+ */
+ public void playSilence(long duration, int queueMode, String[] params) {
+ ArrayList<String> speakingParams = new ArrayList<String>();
+ if (params != null) {
+ speakingParams = new ArrayList<String>(Arrays.asList(params));
+ }
+ mSelf.playSilence(duration, queueMode, speakingParams);
+ }
+
+
+ /**
+ * Stops all speech output and removes any utterances still in the
+ * queue.
+ */
+ public void stop() {
+ mSelf.stop();
+ }
+
+ /**
+ * Returns whether or not the TTS is speaking.
+ *
+ * @return Boolean to indicate whether or not the TTS is speaking
+ */
+ public boolean isSpeaking() {
+ return (mSelf.mIsSpeaking && (mSpeechQueue.size() < 1));
+ }
+
+ /**
+ * Adds a sound resource to the TTS.
+ *
+ * @param text
+ * The text that should be associated with the sound resource
+ * @param packageName
+ * The name of the package which has the sound resource
+ * @param resId
+ * The resource ID of the sound within its package
+ */
+ public void addSpeech(String text, String packageName, int resId) {
+ mSelf.addSpeech(text, packageName, resId);
+ }
+
+ /**
+ * Adds a sound resource to the TTS.
+ *
+ * @param text
+ * The text that should be associated with the sound resource
+ * @param filename
+ * The filename of the sound resource. This must be a
+ * complete path like: (/sdcard/mysounds/mysoundbite.mp3).
+ */
+ public void addSpeechFile(String text, String filename) {
+ mSelf.addSpeech(text, filename);
+ }
+
+ /**
+ * Adds a sound resource to the TTS as an earcon.
+ *
+ * @param earcon
+ * The text that should be associated with the sound resource
+ * @param packageName
+ * The name of the package which has the sound resource
+ * @param resId
+ * The resource ID of the sound within its package
+ */
+ public void addEarcon(String earcon, String packageName, int resId) {
+ mSelf.addEarcon(earcon, packageName, resId);
+ }
+
+ /**
+ * Adds a sound resource to the TTS as an earcon.
+ *
+ * @param earcon
+ * The text that should be associated with the sound resource
+ * @param filename
+ * The filename of the sound resource. This must be a
+ * complete path like: (/sdcard/mysounds/mysoundbite.mp3).
+ */
+ public void addEarconFile(String earcon, String filename) {
+ mSelf.addEarcon(earcon, filename);
+ }
+
+ /**
+ * Sets the speech rate for the TTS. Note that this will only have an
+ * effect on synthesized speech; it will not affect pre-recorded speech.
+ *
+ * @param speechRate
+ * The speech rate that should be used
+ */
+ public void setSpeechRate(int speechRate) {
+ mSelf.setSpeechRate(speechRate);
+ }
+
+ // TODO: Fix comment about language
+ /**
+ * Sets the speech rate for the TTS. Note that this will only have an
+ * effect on synthesized speech; it will not affect pre-recorded speech.
+ *
+ * @param language
+ * The language to be used. The languages are specified by
+ * their IETF language tags as defined by BCP 47. This is the
+ * same standard used for the lang attribute in HTML. See:
+ * http://en.wikipedia.org/wiki/IETF_language_tag
+ */
+ public void setLanguage(String language) {
+ mSelf.setLanguage(language);
+ }
+
+ /**
+ * Speaks the given text using the specified queueing mode and
+ * parameters.
+ *
+ * @param text
+ * The String of text that should be synthesized
+ * @param params
+ * An ArrayList of parameters. The first element of this
+ * array controls the type of voice to use.
+ * @param filename
+ * The string that gives the full output filename; it should
+ * be something like "/sdcard/myappsounds/mysound.wav".
+ * @return A boolean that indicates if the synthesis succeeded
+ */
+ public boolean synthesizeToFile(String text, String[] params,
+ String filename) {
+ ArrayList<String> speakingParams = new ArrayList<String>();
+ if (params != null) {
+ speakingParams = new ArrayList<String>(Arrays.asList(params));
+ }
+ return mSelf.synthesizeToFile(text, speakingParams, filename, true);
+ }
+ };
}
diff --git a/tts/jni/Android.mk b/tts/jni/Android.mk
index bb76583..665d6d2 100755
--- a/tts/jni/Android.mk
+++ b/tts/jni/Android.mk
@@ -14,13 +14,10 @@
libutils \
libcutils
-ifneq ($(TARGET_SIMULATOR),true)
-LOCAL_SHARED_LIBRARIES += \
- libdl
-endif
-
-ifeq ($(TARGET_OS)-$(TARGET_SIMULATOR),linux-true)
-LOCAL_LDLIBS += -ldl
+ifeq ($(TARGET_SIMULATOR),true)
+ LOCAL_LDLIBS += -ldl
+else
+ LOCAL_SHARED_LIBRARIES += libdl
endif