diff options
25 files changed, 1625 insertions, 866 deletions
diff --git a/api/current.xml b/api/current.xml index 3a31034..9e9eaad4 100644 --- a/api/current.xml +++ b/api/current.xml @@ -1145,33 +1145,33 @@ visibility="public" > </field> -<field name="WRITE_GSERVICES" +<field name="WRITE_EXTERNAL_STORAGE" type="java.lang.String" transient="false" volatile="false" - value=""android.permission.WRITE_GSERVICES"" + value=""android.permission.WRITE_EXTERNAL_STORAGE"" static="true" final="true" deprecated="not deprecated" visibility="public" > </field> -<field name="WRITE_OWNER_DATA" +<field name="WRITE_GSERVICES" type="java.lang.String" transient="false" volatile="false" - value=""android.permission.WRITE_OWNER_DATA"" + value=""android.permission.WRITE_GSERVICES"" static="true" final="true" deprecated="not deprecated" visibility="public" > </field> -<field name="WRITE_EXTERNAL_STORAGE" +<field name="WRITE_OWNER_DATA" type="java.lang.String" transient="false" volatile="false" - value=""android.permission.WRITE_EXTERNAL_STORAGE"" + value=""android.permission.WRITE_OWNER_DATA"" static="true" final="true" deprecated="not deprecated" @@ -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 @@ class ApplicationContext extends Context { } @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.text.TextUtils; 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 @@ class SuggestionsAdapter extends ResourceCursorAdapter { // 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 @@ interface IPackageManager { 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 @@ public abstract class PackageManager { 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.FileDescriptor; 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 @@ public class AssetFileDescriptor implements Parcelable { } /** + * 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 @@ public class AssetFileDescriptor implements Parcelable { * 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 @@ public class AssetFileDescriptor implements Parcelable { } /** + * 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 @@ public class AssetFileDescriptor implements Parcelable { 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 @@ public class GestureOverlayView extends FrameLayout { // 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 @@ public class GestureOverlayView extends FrameLayout { 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 @@ public class GestureOverlayView extends FrameLayout { 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 @@ public class GestureOverlayView extends FrameLayout { 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 @@ public class GestureOverlayView extends FrameLayout { 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 @@ public class GestureOverlayView extends FrameLayout { 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 @@ public class GestureOverlayView extends FrameLayout { } 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 class GestureOverlayView extends FrameLayout { } } + 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 @@ public class MemoryFile { 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 @@ public class MemoryFile 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 class MemoryFile 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 @@ public class MemoryFile } } - 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 @@ public class MemoryFile * @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 @@ public class MemoryFile 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 @@ public class MemoryFile } 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 @@ static jobject android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstring na 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 void android_os_MemoryFile_pin(JNIEnv* env, jobject clazz, jobject fileDe } } +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..f67f04c 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 @@ import android.view.MotionEvent; 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 @@ public class ClearActivity extends Activity { @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 @@ class ClearRenderer implements GLSurfaceView.Renderer { } 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 @@ package com.android.server; 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 @@ class PackageManagerService extends IPackageManager.Stub { } } + 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 @@ class RILRequest { } void - onError(int error) { + onError(int error, Object ret) { CommandException ex; ex = CommandException.fromRilErrno(error); @@ -175,7 +175,7 @@ class RILRequest { + " error: " + ex); if (mResult != null) { - AsyncResult.forMessage(mResult, null, ex); + AsyncResult.forMessage(mResult, ret, ex); mResult.sendToTarget(); } @@ -290,7 +290,7 @@ public final class RIL extends BaseCommands implements CommandsInterface { 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 @@ public final class RIL extends BaseCommands implements CommandsInterface { // 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 @@ public final class RIL extends BaseCommands implements CommandsInterface { // 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 @@ public final class RIL extends BaseCommands implements CommandsInterface { 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 @@ public final class RIL extends BaseCommands implements CommandsInterface { 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 @@ public final class RIL extends BaseCommands implements CommandsInterface { 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 @@ public class MockPackageManager extends PackageManager { 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 @@ public class MemoryFileTest extends TestCase { 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 @@ public class MemoryFileTest extends TestCase { } } + @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 @@ def main(options, args): 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 @@ def main(options, args): # 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 @@ def main(options, args): 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 @@ def main(options, args): 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 @@ public class LayoutTestsAutoRunner extends InstrumentationTestRunner { } } + 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 class LayoutTestsAutoRunner extends InstrumentationTestRunner { 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 @@ public class ReliabilityTest extends ActivityInstrumentationTestCase2<Reliabilit 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 @@ public class ReliabilityTest extends ActivityInstrumentationTestCase2<Reliabilit 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 @@ public class ReliabilityTest extends ActivityInstrumentationTestCase2<Reliabilit 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 @@ public class ReliabilityTestActivity extends Activity { 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 @@ public class ReliabilityTestActivity extends Activity { handleTimeout(); return; case MSG_NAVIGATE: + manualDelay = msg.arg2; navigate((String)msg.obj, msg.arg1); return; } @@ -212,6 +215,12 @@ public class ReliabilityTestActivity extends Activity { } @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 class ReliabilityTestActivity extends Activity { 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 @@ public class SynthProxy { } 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..d317181 100755 --- a/tts/java/android/tts/TtsService.java +++ b/tts/java/android/tts/TtsService.java @@ -45,635 +45,158 @@ import java.util.concurrent.locks.ReentrantLock; */ 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; - - 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; - } + 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 SoundResource(String file) { - mSourcePackageName = null; - mResId = -1; - mFilename = file; + public SpeechItem(long silenceTime) { + mDuration = silenceTime; + } } - } - 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; + /** + * 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; + + public SoundResource(String packageName, int id) { + mSourcePackageName = packageName; + mResId = id; + mFilename = null; + } - private SharedPreferences prefs; + public SoundResource(String file) { + mSourcePackageName = null; + mResId = -1; + mFilename = file; + } + } - private final ReentrantLock speechQueueLock = new ReentrantLock(); - private final ReentrantLock synthesizerLock = new ReentrantLock(); + 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"; - // TODO support multiple SpeechSynthesis objects - private SynthProxy nativeSynth; + final RemoteCallbackList<ITtsCallback> mCallbacks = new RemoteCallbackList<ITtsCallback>(); - @Override - public void onCreate() { - super.onCreate(); - Log.i("TTS", "TTS starting"); + 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; - // TODO: Make this work when the settings are done in the main Settings - // app. - prefs = PreferenceManager.getDefaultSharedPreferences(this); + private final ReentrantLock speechQueueLock = new ReentrantLock(); + private final ReentrantLock synthesizerLock = new ReentrantLock(); - // TODO: This should be changed to work by requesting the path - // from the default engine. - nativeSynth = new SynthProxy(prefs.getString("engine_pref", "")); + // TODO support multiple SpeechSynthesis objects + private SynthProxy nativeSynth; + @Override + public void onCreate() { + super.onCreate(); + Log.i("TTS", "TTS starting"); - self = this; - isSpeaking = false; - earcons = new HashMap<String, SoundResource>(); - utterances = new HashMap<String, SoundResource>(); + // TODO: Make this work when the settings are done in the main Settings + // app. + prefs = PreferenceManager.getDefaultSharedPreferences(this); - speechQueue = new ArrayList<SpeechItem>(); - player = null; + // TODO: This should be changed to work by requesting the path + // from the default engine. + nativeSynth = new SynthProxy(prefs.getString("engine_pref", "")); - 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(); + mSelf = this; + mIsSpeaking = false; - nativeSynth.shutdown(); + mEarcons = new HashMap<String, SoundResource>(); + mUtterances = new HashMap<String, SoundResource>(); - // Unregister all callbacks. - mCallbacks.kill(); - } + mSpeechQueue = new ArrayList<SpeechItem>(); + mPlayer = null; - 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"); + setLanguage(prefs.getString("lang_pref", "en-rUS")); + setSpeechRate(Integer.parseInt(prefs.getString("rate_pref", "140"))); } - 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"); - } + @Override + public void onDestroy() { + super.onDestroy(); + // Don't hog the media player + cleanUpPlayer(); - public void onCompletion(MediaPlayer arg0) { - processSpeechQueue(); - } + nativeSynth.shutdown(); - private void playSilence(long duration, int queueMode, - ArrayList<String> params) { - if (queueMode == 0) { - stop(); + // Unregister all callbacks. + mCallbacks.kill(); } - 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(); + 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")); } - } - } - 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 = earcons.get(text); - } else { - sr = utterances.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. - } + nativeSynth.setSpeechRate(rate); } - 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); - } - } else { - 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)); - } - // 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; - } - } - 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; - } - } - } - return null; - } - - private final ITts.Stub mBinder = new Stub() { - - public void registerCallback(ITtsCallback cb) { - if (cb != null) - mCallbacks.register(cb); + nativeSynth.setLanguage(lang); } - 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) { - self.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) { - self.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)); - } - self.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)); - } - self.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)); - } - self.playSilence(duration, queueMode, speakingParams); + 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); } - - /** - * Stops all speech output and removes any utterances still in the - * queue. - */ - public void stop() { - self.stop(); + private void setEngine(Intent engineIntent) { + // TODO: Implement engine selection code here. } - /** - * 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)); + private int getEngineStatus() { + // TODO: Proposal - add a sanity check method that + // TTS engine plugins must implement. + return 0; } /** @@ -686,8 +209,8 @@ public class TtsService extends Service implements OnCompletionListener { * @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); + private void addSpeech(String text, String packageName, int resId) { + mUtterances.put(text, new SoundResource(packageName, resId)); } /** @@ -696,11 +219,11 @@ public class TtsService extends Service implements OnCompletionListener { * @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). + * 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); + private void addSpeech(String text, String filename) { + mUtterances.put(text, new SoundResource(filename)); } /** @@ -713,8 +236,8 @@ public class TtsService extends Service implements OnCompletionListener { * @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); + private void addEarcon(String earcon, String packageName, int resId) { + mEarcons.put(earcon, new SoundResource(packageName, resId)); } /** @@ -723,61 +246,538 @@ public class TtsService extends Service implements OnCompletionListener { * @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). + * 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); + private void addEarcon(String earcon, String filename) { + mEarcons.put(earcon, new SoundResource(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. + * Speaks the given text using the specified queueing mode and parameters. * - * @param speechRate - * The speech rate that should be used + * @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. */ - public void setSpeechRate(int speechRate) { - self.setSpeechRate(speechRate); + 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(); + } } - // 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. + * Plays the earcon using the specified queueing mode and parameters. * - * @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 + * @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. */ - public void setLanguage(String language) { - self.setLanguage(language); + 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(); + } + } + + /** + * Stops all speech output and removes any utterances still in the queue. + */ + 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(); + } + + 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; + } } /** - * 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 @@ LOCAL_SHARED_LIBRARIES := \ 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 |
