Merge change 3013 into donut

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