Merge "Change the OpenGL viewport when new layout params are used. Bug #3303158"
diff --git a/api/current.xml b/api/current.xml
index af6d2cd..0f461a2 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -138970,7 +138970,7 @@
  synchronized="false"
  static="true"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -139014,7 +139014,7 @@
  synchronized="false"
  static="true"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -139025,7 +139025,7 @@
  synchronized="false"
  static="true"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -139036,7 +139036,7 @@
  synchronized="false"
  static="true"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -139047,7 +139047,7 @@
  synchronized="false"
  static="true"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -139058,7 +139058,7 @@
  synchronized="false"
  static="true"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -139069,7 +139069,7 @@
  synchronized="false"
  static="true"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -139080,7 +139080,7 @@
  synchronized="false"
  static="true"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -139091,7 +139091,7 @@
  synchronized="false"
  static="true"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -139102,7 +139102,7 @@
  synchronized="false"
  static="true"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -139113,7 +139113,7 @@
  synchronized="false"
  static="true"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -139124,7 +139124,7 @@
  synchronized="false"
  static="true"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -256580,7 +256580,7 @@
  return="void"
  abstract="false"
  native="false"
- synchronized="true"
+ synchronized="false"
  static="false"
  final="false"
  deprecated="not deprecated"
@@ -256595,7 +256595,7 @@
  return="void"
  abstract="false"
  native="false"
- synchronized="true"
+ synchronized="false"
  static="false"
  final="false"
  deprecated="not deprecated"
@@ -256729,7 +256729,7 @@
  return="java.beans.PropertyChangeListener[]"
  abstract="false"
  native="false"
- synchronized="true"
+ synchronized="false"
  static="false"
  final="false"
  deprecated="not deprecated"
@@ -256742,7 +256742,7 @@
  return="java.beans.PropertyChangeListener[]"
  abstract="false"
  native="false"
- synchronized="true"
+ synchronized="false"
  static="false"
  final="false"
  deprecated="not deprecated"
@@ -256753,7 +256753,7 @@
  return="boolean"
  abstract="false"
  native="false"
- synchronized="true"
+ synchronized="false"
  static="false"
  final="false"
  deprecated="not deprecated"
@@ -256766,7 +256766,7 @@
  return="void"
  abstract="false"
  native="false"
- synchronized="true"
+ synchronized="false"
  static="false"
  final="false"
  deprecated="not deprecated"
@@ -256781,7 +256781,7 @@
  return="void"
  abstract="false"
  native="false"
- synchronized="true"
+ synchronized="false"
  static="false"
  final="false"
  deprecated="not deprecated"
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index 9a8f2d2..d15fb88 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -18,14 +18,20 @@
 
 import android.content.res.Resources;
 import android.database.sqlite.SQLiteClosable;
+import android.os.Binder;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.Process;
+import android.util.Log;
+import android.util.SparseIntArray;
 
 /**
  * A buffer containing multiple cursor rows.
  */
 public class CursorWindow extends SQLiteClosable implements Parcelable {
+    private static final String STATS_TAG = "CursorWindowStats";
+
     /** The cursor window size. resource xml file specifies the value in kB.
      * convert it to bytes here by multiplying with 1024.
      */
@@ -33,8 +39,9 @@
         Resources.getSystem().getInteger(
                 com.android.internal.R.integer.config_cursorWindowSize) * 1024;
 
-    /** The pointer to the native window class */
-    @SuppressWarnings("unused")
+    /** The pointer to the native window class. set by the native methods in
+     * android_database_CursorWindow.cpp
+     */
     private int nWindow;
 
     private int mStartPos;
@@ -46,7 +53,18 @@
      */
     public CursorWindow(boolean localWindow) {
         mStartPos = 0;
-        native_init(sCursorWindowSize, localWindow);
+        int rslt = native_init(sCursorWindowSize, localWindow);
+        printDebugMsgIfError(rslt);
+        recordNewWindow(Binder.getCallingPid(), nWindow);
+    }
+
+    private void printDebugMsgIfError(int rslt) {
+        if (rslt > 0) {
+            // cursor window allocation failed. either low memory or too many cursors being open.
+            // print info to help in debugging this.
+            throw new CursorWindowAllocationException("Cursor Window allocation of " +
+                    sCursorWindowSize/1024 + " kb failed. " + printStats());
+        }
     }
 
     /**
@@ -574,21 +592,78 @@
     private CursorWindow(Parcel source) {
         IBinder nativeBinder = source.readStrongBinder();
         mStartPos = source.readInt();
-
-        native_init(nativeBinder);
+        int rslt = native_init(nativeBinder);
+        printDebugMsgIfError(rslt);
     }
 
     /** Get the binder for the native side of the window */
     private native IBinder native_getBinder();
 
     /** Does the native side initialization for an empty window */
-    private native void native_init(int cursorWindowSize, boolean localOnly);
+    private native int native_init(int cursorWindowSize, boolean localOnly);
 
     /** Does the native side initialization with an existing binder from another process */
-    private native void native_init(IBinder nativeBinder);
+    private native int native_init(IBinder nativeBinder);
 
     @Override
     protected void onAllReferencesReleased() {
-        close_native();        
+        int windowId = nWindow;
+        recordClosingOfWindow(Binder.getCallingPid(), nWindow);
+        close_native();
+    }
+
+    private static final SparseIntArray sWindowToPidMap = new SparseIntArray();
+
+    private void recordNewWindow(int pid, int window) {
+        synchronized (sWindowToPidMap) {
+            sWindowToPidMap.put(window, pid);
+            if (Log.isLoggable(STATS_TAG, Log.VERBOSE)) {
+                Log.i(STATS_TAG, "Created a new Cursor. " + printStats());
+            }
+        }
+    }
+
+    private void recordClosingOfWindow(int pid, int window) {
+        synchronized (sWindowToPidMap) {
+            if (sWindowToPidMap.size() == 0) {
+                // this means we are not in the ContentProvider.
+                return;
+            }
+            sWindowToPidMap.delete(window);
+        }
+    }
+    private String printStats() {
+        StringBuilder buff = new StringBuilder();
+        int myPid = Process.myPid();
+        int total = 0;
+        SparseIntArray pidCounts = new SparseIntArray();
+        synchronized (sWindowToPidMap) {
+            int size = sWindowToPidMap.size();
+            if (size == 0) {
+                // this means we are not in the ContentProvider.
+                return "";
+            }
+            for (int indx = 0; indx < size; indx++) {
+                int pid = sWindowToPidMap.valueAt(indx);
+                int value = pidCounts.get(pid);
+                pidCounts.put(pid, ++value);
+            }
+        }
+        int numPids = pidCounts.size();
+        for (int i = 0; i < numPids;i++) {
+            buff.append(" (# cursors opened by ");
+            int pid = pidCounts.keyAt(i);
+            if (pid == myPid) {
+                buff.append("this proc=");
+            } else {
+                buff.append("pid " + pid + "=");
+            }
+            int num = pidCounts.get(pid);
+            buff.append(num + ")");
+            total += num;
+        }
+        // limit the returned string size to 1000
+        String s = (buff.length() > 980) ? buff.substring(0, 980) : buff.toString();
+        return "# Open Cursors=" + total + s;
     }
 }
diff --git a/core/java/android/database/CursorWindowAllocationException.java b/core/java/android/database/CursorWindowAllocationException.java
new file mode 100644
index 0000000..ba7df68
--- /dev/null
+++ b/core/java/android/database/CursorWindowAllocationException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database;
+
+/**
+ * This exception is thrown when a CursorWindow couldn't be allocated,
+ * most probably due to memory not being available
+ */
+class CursorWindowAllocationException extends java.lang.RuntimeException
+{
+    public CursorWindowAllocationException()
+    {
+        super();
+    }
+
+    public CursorWindowAllocationException(String description)
+    {
+        super(description);
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java
index 93f9a71e..01e9fb3 100644
--- a/core/java/android/database/sqlite/SQLiteClosable.java
+++ b/core/java/android/database/sqlite/SQLiteClosable.java
@@ -38,20 +38,22 @@
     }
 
     public void releaseReference() {
+        boolean refCountIsZero = false;
         synchronized(this) {
-            mReferenceCount--;
-            if (mReferenceCount == 0) {
-                onAllReferencesReleased();
-            }
+            refCountIsZero = --mReferenceCount == 0;
+        }
+        if (refCountIsZero) {
+            onAllReferencesReleased();
         }
     }
 
     public void releaseReferenceFromContainer() {
+        boolean refCountIsZero = false;
         synchronized(this) {
-            mReferenceCount--;
-            if (mReferenceCount == 0) {
-                onAllReferencesReleasedFromContainer();
-            }
+            refCountIsZero = --mReferenceCount == 0;
+        }
+        if (refCountIsZero) {
+            onAllReferencesReleasedFromContainer();
         }
     }
 
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 6f59dc9..d8bc266 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -991,8 +991,10 @@
             }
             return db;
         } catch (SQLiteDatabaseCorruptException e) {
-            db.mErrorHandler.onCorruption(db);
-            return SQLiteDatabase.openDatabase(path, factory, flags, errorHandler);
+            return handleCorruptedDatabase(db, path, factory, flags, errorHandler);
+        } catch (SQLiteCantOpenDatabaseException e) {
+            Log.e(TAG, "database file can't be opened. possibly due to database corruption.");
+            return handleCorruptedDatabase(db, path, factory, flags, errorHandler);
         } catch (SQLiteException e) {
             Log.e(TAG, "Failed to open the database. closing it.", e);
             db.close();
@@ -1000,6 +1002,12 @@
         }
     }
 
+    private static SQLiteDatabase handleCorruptedDatabase(SQLiteDatabase db, String path,
+            CursorFactory factory, int flags, DatabaseErrorHandler errorHandler) {
+        db.mErrorHandler.onCorruption(db);
+        return SQLiteDatabase.openDatabase(path, factory, flags, errorHandler);
+    }
+
     /**
      * Equivalent to openDatabase(file.getPath(), factory, CREATE_IF_NECESSARY).
      */
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 8d9867e7..40d9cff 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -559,18 +559,55 @@
         /* cumulative elapsed time for class initialization, in usec */
         return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_TIME);
     }
+
+    /**
+     * Returns the global count of external allocation requests.  Now
+     * that external allocation tracking no longer exists this method
+     * always returns 0.
+     *
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
     public static int getGlobalExternalAllocCount() {
-        return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_EXT_ALLOCATED_OBJECTS);
+        return 0;
     }
+
+    /**
+     * Returns the global count of bytes externally allocated.  Now
+     * that external allocation tracking no longer exists this method
+     * always returns 0.
+     *
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
     public static int getGlobalExternalAllocSize() {
-        return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_EXT_ALLOCATED_BYTES);
+        return 0;
     }
+
+    /**
+     * Returns the global count of freed external allocation requests.
+     * Now that external allocation tracking no longer exists this
+     * method always returns 0.
+     *
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
     public static int getGlobalExternalFreedCount() {
-        return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_EXT_FREED_OBJECTS);
+        return 0;
     }
+
+    /**
+     * Returns the global count of freed bytes from external
+     * allocation requests.  Now that external allocation tracking no
+     * longer exists this method always returns 0.
+     *
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
     public static int getGlobalExternalFreedSize() {
-        return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_EXT_FREED_BYTES);
+        return 0;
     }
+
     public static int getGlobalGcInvocationCount() {
         return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_GC_INVOCATIONS);
     }
@@ -580,12 +617,31 @@
     public static int getThreadAllocSize() {
         return VMDebug.getAllocCount(VMDebug.KIND_THREAD_ALLOCATED_BYTES);
     }
+
+    /**
+     * Returns the count of external allocation requests made by the
+     * current thread.  Now that external allocation tracking no
+     * longer exists this method always returns 0.
+     *
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
     public static int getThreadExternalAllocCount() {
-        return VMDebug.getAllocCount(VMDebug.KIND_THREAD_EXT_ALLOCATED_OBJECTS);
+        return 0;
     }
+
+    /**
+     * Returns the global count of bytes externally allocated.  Now
+     * that external allocation tracking no longer exists this method
+     * always returns 0.
+     *
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
     public static int getThreadExternalAllocSize() {
-        return VMDebug.getAllocCount(VMDebug.KIND_THREAD_EXT_ALLOCATED_BYTES);
+        return 0;
     }
+
     public static int getThreadGcInvocationCount() {
         return VMDebug.getAllocCount(VMDebug.KIND_THREAD_GC_INVOCATIONS);
     }
@@ -608,18 +664,47 @@
     public static void resetGlobalClassInitTime() {
         VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_TIME);
     }
-    public static void resetGlobalExternalAllocCount() {
-        VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_EXT_ALLOCATED_OBJECTS);
-    }
-    public static void resetGlobalExternalAllocSize() {
-        VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_EXT_ALLOCATED_BYTES);
-    }
-    public static void resetGlobalExternalFreedCount() {
-        VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_EXT_FREED_OBJECTS);
-    }
-    public static void resetGlobalExternalFreedSize() {
-        VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_EXT_FREED_BYTES);
-    }
+
+    /**
+     * Resets the global count of external allocation requests.  Now
+     * that external allocation tracking has been removed this method
+     * has no effect.
+     *
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public static void resetGlobalExternalAllocCount() {}
+
+    /**
+     * Resets the global count of bytes externally allocated.  Now
+     * that external allocation tracking has been removed this method
+     * has no effect.
+     *
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public static void resetGlobalExternalAllocSize() {}
+
+    /**
+     * Resets the global count of freed external allocations.  Now
+     * that external allocation tracking has been removed this method
+     * has no effect.
+     *
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public static void resetGlobalExternalFreedCount() {}
+
+    /**
+     * Resets the global count counter of freed bytes from external
+     * allocations.  Now that external allocation tracking has been
+     * removed this method has no effect.
+     *
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public static void resetGlobalExternalFreedSize() {}
+
     public static void resetGlobalGcInvocationCount() {
         VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_GC_INVOCATIONS);
     }
@@ -629,12 +714,27 @@
     public static void resetThreadAllocSize() {
         VMDebug.resetAllocCount(VMDebug.KIND_THREAD_ALLOCATED_BYTES);
     }
-    public static void resetThreadExternalAllocCount() {
-        VMDebug.resetAllocCount(VMDebug.KIND_THREAD_EXT_ALLOCATED_OBJECTS);
-    }
-    public static void resetThreadExternalAllocSize() {
-        VMDebug.resetAllocCount(VMDebug.KIND_THREAD_EXT_ALLOCATED_BYTES);
-    }
+
+    /**
+     * Resets the count of external allocation requests made by the
+     * current thread.  Now that external allocation tracking has been
+     * removed this method has no effect.
+     *
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public static void resetThreadExternalAllocCount() {}
+
+    /**
+     * Resets the count of bytes externally allocated by the current
+     * thread.  Now that external allocation tracking has been removed
+     * this method has no effect.
+     *
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public static void resetThreadExternalAllocSize() {}
+
     public static void resetThreadGcInvocationCount() {
         VMDebug.resetAllocCount(VMDebug.KIND_THREAD_GC_INVOCATIONS);
     }
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index d8d63206..3dd1ecd 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -361,103 +361,101 @@
                             if (fitbottom > okbottom)
                                 okbottom = fitbottom;
                         }
-                    } else {
-                        if (breakOnlyAtSpaces) {
-                            if (ok != here) {
-                                // Log.e("text", "output ok " + here + " to " +ok);
+                    } else if (breakOnlyAtSpaces) {
+                        if (ok != here) {
+                            // Log.e("text", "output ok " + here + " to " +ok);
 
-                                while (ok < spanEnd && chs[ok - paraStart] == ' ') {
-                                    ok++;
-                                }
-
-                                v = out(source,
-                                        here, ok,
-                                        okascent, okdescent, oktop, okbottom,
-                                        v,
-                                        spacingmult, spacingadd, chooseht,
-                                        choosehtv, fm, hasTabOrEmoji,
-                                        needMultiply, paraStart, chdirs, dir, easy,
-                                        ok == bufend, includepad, trackpad,
-                                        chs, widths, here - paraStart,
-                                        where, ellipsizedWidth, okwidth,
-                                        paint);
-
-                                here = ok;
-                            } else {
-                                // Act like it fit even though it didn't.
-
-                                fitwidth = w;
-                                fit = j + 1;
-
-                                if (fmtop < fittop)
-                                    fittop = fmtop;
-                                if (fmascent < fitascent)
-                                    fitascent = fmascent;
-                                if (fmdescent > fitdescent)
-                                    fitdescent = fmdescent;
-                                if (fmbottom > fitbottom)
-                                    fitbottom = fmbottom;
+                            while (ok < spanEnd && chs[ok - paraStart] == ' ') {
+                                ok++;
                             }
+
+                            v = out(source,
+                                    here, ok,
+                                    okascent, okdescent, oktop, okbottom,
+                                    v,
+                                    spacingmult, spacingadd, chooseht,
+                                    choosehtv, fm, hasTabOrEmoji,
+                                    needMultiply, paraStart, chdirs, dir, easy,
+                                    ok == bufend, includepad, trackpad,
+                                    chs, widths, here - paraStart,
+                                    where, ellipsizedWidth, okwidth,
+                                    paint);
+
+                            here = ok;
                         } else {
-                            if (ok != here) {
-                                // Log.e("text", "output ok " + here + " to " +ok);
+                            // Act like it fit even though it didn't.
 
-                                while (ok < spanEnd && chs[ok - paraStart] == ' ') {
-                                    ok++;
-                                }
+                            fitwidth = w;
+                            fit = j + 1;
 
-                                v = out(source,
-                                        here, ok,
-                                        okascent, okdescent, oktop, okbottom,
-                                        v,
-                                        spacingmult, spacingadd, chooseht,
-                                        choosehtv, fm, hasTabOrEmoji,
-                                        needMultiply, paraStart, chdirs, dir, easy,
-                                        ok == bufend, includepad, trackpad,
-                                        chs, widths, here - paraStart,
-                                        where, ellipsizedWidth, okwidth,
-                                        paint);
+                            if (fmtop < fittop)
+                                fittop = fmtop;
+                            if (fmascent < fitascent)
+                                fitascent = fmascent;
+                            if (fmdescent > fitdescent)
+                                fitdescent = fmdescent;
+                            if (fmbottom > fitbottom)
+                                fitbottom = fmbottom;
+                        }
+                    } else {
+                        if (ok != here) {
+                            // Log.e("text", "output ok " + here + " to " +ok);
 
-                                here = ok;
-                            } else if (fit != here) {
-                                // Log.e("text", "output fit " + here + " to " +fit);
-                                v = out(source,
-                                        here, fit,
-                                        fitascent, fitdescent,
-                                        fittop, fitbottom,
-                                        v,
-                                        spacingmult, spacingadd, chooseht,
-                                        choosehtv, fm, hasTabOrEmoji,
-                                        needMultiply, paraStart, chdirs, dir, easy,
-                                        fit == bufend, includepad, trackpad,
-                                        chs, widths, here - paraStart,
-                                        where, ellipsizedWidth, fitwidth,
-                                        paint);
-
-                                here = fit;
-                            } else {
-                                // Log.e("text", "output one " + here + " to " +(here + 1));
-                                // XXX not sure why the existing fm wasn't ok.
-                                // measureText(paint, mWorkPaint,
-                                //             source, here, here + 1, fm, tab,
-                                //             null);
-
-                                v = out(source,
-                                        here, here+1,
-                                        fm.ascent, fm.descent,
-                                        fm.top, fm.bottom,
-                                        v,
-                                        spacingmult, spacingadd, chooseht,
-                                        choosehtv, fm, hasTabOrEmoji,
-                                        needMultiply, paraStart, chdirs, dir, easy,
-                                        here + 1 == bufend, includepad,
-                                        trackpad,
-                                        chs, widths, here - paraStart,
-                                        where, ellipsizedWidth,
-                                        widths[here - paraStart], paint);
-
-                                here = here + 1;
+                            while (ok < spanEnd && chs[ok - paraStart] == ' ') {
+                                ok++;
                             }
+
+                            v = out(source,
+                                    here, ok,
+                                    okascent, okdescent, oktop, okbottom,
+                                    v,
+                                    spacingmult, spacingadd, chooseht,
+                                    choosehtv, fm, hasTabOrEmoji,
+                                    needMultiply, paraStart, chdirs, dir, easy,
+                                    ok == bufend, includepad, trackpad,
+                                    chs, widths, here - paraStart,
+                                    where, ellipsizedWidth, okwidth,
+                                    paint);
+
+                            here = ok;
+                        } else if (fit != here) {
+                            // Log.e("text", "output fit " + here + " to " +fit);
+                            v = out(source,
+                                    here, fit,
+                                    fitascent, fitdescent,
+                                    fittop, fitbottom,
+                                    v,
+                                    spacingmult, spacingadd, chooseht,
+                                    choosehtv, fm, hasTabOrEmoji,
+                                    needMultiply, paraStart, chdirs, dir, easy,
+                                    fit == bufend, includepad, trackpad,
+                                    chs, widths, here - paraStart,
+                                    where, ellipsizedWidth, fitwidth,
+                                    paint);
+
+                            here = fit;
+                        } else {
+                            // Log.e("text", "output one " + here + " to " +(here + 1));
+                            // XXX not sure why the existing fm wasn't ok.
+                            // measureText(paint, mWorkPaint,
+                            //             source, here, here + 1, fm, tab,
+                            //             null);
+
+                            v = out(source,
+                                    here, here+1,
+                                    fm.ascent, fm.descent,
+                                    fm.top, fm.bottom,
+                                    v,
+                                    spacingmult, spacingadd, chooseht,
+                                    choosehtv, fm, hasTabOrEmoji,
+                                    needMultiply, paraStart, chdirs, dir, easy,
+                                    here + 1 == bufend, includepad,
+                                    trackpad,
+                                    chs, widths, here - paraStart,
+                                    where, ellipsizedWidth,
+                                    widths[here - paraStart], paint);
+
+                            here = here + 1;
                         }
 
                         if (here < spanStart) {
diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java
index 80c8169..9e73371 100644
--- a/core/java/android/text/method/LinkMovementMethod.java
+++ b/core/java/android/text/method/LinkMovementMethod.java
@@ -16,10 +16,13 @@
 
 package android.text.method;
 
+import android.text.Layout;
+import android.text.NoCopySpan;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.style.ClickableSpan;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
-import android.text.*;
-import android.text.style.*;
 import android.view.View;
 import android.widget.TextView;
 
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 45927f9..43ec378 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -2577,10 +2577,7 @@
                 .sendToTarget();
     }
 
-    // called by JNI.  PluginWidget functions for creating an embedded View for
-    // the surface drawing model.
-    private ViewManager.ChildView addSurface(View pluginView, int x, int y,
-                                             int width, int height) {
+    private ViewManager.ChildView createSurface(View pluginView) {
         if (mWebView == null) {
             return null;
         }
@@ -2598,6 +2595,14 @@
 
         ViewManager.ChildView view = mWebView.mViewManager.createView();
         view.mView = pluginView;
+        return view;
+    }
+    
+    // called by JNI.  PluginWidget functions for creating an embedded View for
+    // the surface drawing model.
+    private ViewManager.ChildView addSurface(View pluginView, int x, int y,
+                                             int width, int height) {
+        ViewManager.ChildView view = createSurface(pluginView);
         view.attachView(x, y, width, height);
         return view;
     }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index ae4b193..76d4835 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -7639,6 +7639,7 @@
                 type == Character.LOWERCASE_LETTER ||
                 type == Character.TITLECASE_LETTER ||
                 type == Character.MODIFIER_LETTER ||
+                type == Character.OTHER_LETTER || // Should handle asian characters
                 type == Character.DECIMAL_DIGIT_NUMBER);
     }
 
@@ -7647,15 +7648,15 @@
      *
      * @param offset An offset in the text.
      * @return The offsets for the start and end of the word located at <code>offset</code>.
-     * The two ints offsets are packed in a long, with the starting offset shifted by 32 bits.
-     * Returns a negative value if no valid word was found.
+     * The two ints offsets are packed in a long using {@link #packRangeInLong(int, int)}.
+     * Returns -1 if no valid word was found.
      */
     private long getWordLimitsAt(int offset) {
         int klass = mInputType & InputType.TYPE_MASK_CLASS;
         int variation = mInputType & InputType.TYPE_MASK_VARIATION;
 
         // Text selection is not permitted in password fields
-        if (isPasswordInputType(mInputType) || isVisiblePasswordInputType(mInputType)) {
+        if (hasPasswordTransformationMethod()) {
             return -1;
         }
 
@@ -7739,17 +7740,16 @@
         }
 
         if (hasPasswordTransformationMethod()) {
-            // selectCurrentWord is not available on a password field and would return an
-            // arbitrary 10-charater selection around pressed position. Select all instead.
+            // Always select all on a password field.
             // Cut/copy menu entries are not available for passwords, but being able to select all
             // is however useful to delete or paste to replace the entire content.
             selectAll();
             return;
         }
 
-        long lastTouchOffset = getLastTouchOffsets();
-        final int minOffset = extractRangeStartFromLong(lastTouchOffset);
-        final int maxOffset = extractRangeEndFromLong(lastTouchOffset);
+        long lastTouchOffsets = getLastTouchOffsets();
+        final int minOffset = extractRangeStartFromLong(lastTouchOffsets);
+        final int maxOffset = extractRangeEndFromLong(lastTouchOffsets);
 
         int selectionStart, selectionEnd;
 
@@ -7979,43 +7979,48 @@
     /**
      * Prepare text so that there are not zero or two spaces at beginning and end of region defined
      * by [min, max] when replacing this region by paste.
-     * Note that if there was two spaces (or more) at that position before, they are kept. We just
+     * Note that if there were two spaces (or more) at that position before, they are kept. We just
      * make sure we do not add an extra one from the paste content.
      */
     private long prepareSpacesAroundPaste(int min, int max, CharSequence paste) {
-        // Paste adds/removes spaces before or after insertion as needed.
-        if (paste.length() > 0 && Character.isSpaceChar(paste.charAt(0))) {
-            if (min > 0 && Character.isSpaceChar(mTransformed.charAt(min - 1))) {
-                // Two spaces at beginning of paste: remove one
-                final int originalLength = mText.length();
-                ((Editable) mText).delete(min - 1, min);
-                // Due to filters, there is no guarantee that exactly one character was
-                // removed. Count instead.
-                final int delta = mText.length() - originalLength;
-                min += delta;
-                max += delta;
-            }
-        } else {
-            if (min > 0 && !Character.isSpaceChar(mTransformed.charAt(min - 1))) {
-                // No space at beginning of paste: add one
-                final int originalLength = mText.length();
-                ((Editable) mText).replace(min, min, " ");
-                // Taking possible filters into account as above.
-                final int delta = mText.length() - originalLength;
-                min += delta;
-                max += delta;
-            }
-        }
+        if (paste.length() > 0) {
+            if (min > 0) {
+                final char charBefore = mTransformed.charAt(min - 1);
+                final char charAfter = paste.charAt(0);
 
-        if (paste.length() > 0 && Character.isSpaceChar(paste.charAt(paste.length() - 1))) {
-            if (max < mText.length() && Character.isSpaceChar(mTransformed.charAt(max))) {
-                // Two spaces at end of paste: remove one
-                ((Editable) mText).delete(max, max + 1);
+                if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
+                    // Two spaces at beginning of paste: remove one
+                    final int originalLength = mText.length();
+                    ((Editable) mText).delete(min - 1, min);
+                    // Due to filters, there is no guarantee that exactly one character was
+                    // removed: count instead.
+                    final int delta = mText.length() - originalLength;
+                    min += delta;
+                    max += delta;
+                } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
+                        !Character.isSpaceChar(charAfter) && charAfter != '\n') {
+                    // No space at beginning of paste: add one
+                    final int originalLength = mText.length();
+                    ((Editable) mText).replace(min, min, " ");
+                    // Taking possible filters into account as above.
+                    final int delta = mText.length() - originalLength;
+                    min += delta;
+                    max += delta;
+                }
             }
-        } else {
-            if (max < mText.length() && !Character.isSpaceChar(mTransformed.charAt(max))) {
-                // No space at end of paste: add one
-                ((Editable) mText).replace(max, max, " ");
+
+            if (max < mText.length()) {
+                final char charBefore = paste.charAt(paste.length() - 1);
+                final char charAfter = mTransformed.charAt(max);
+
+                if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
+                    // Two spaces at end of paste: remove one
+                    ((Editable) mText).delete(max, max + 1);
+                } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
+                        !Character.isSpaceChar(charAfter) && charAfter != '\n') {
+                    // No space at end of paste: add one
+                    ((Editable) mText).replace(max, max, " ");
+                }
             }
         }
 
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index c4cd2a6..a428151 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -50,7 +50,7 @@
     return GET_WINDOW(env, javaWindow);
 }
 
-static void native_init_empty(JNIEnv * env, jobject object, jint cursorWindowSize,
+static jint native_init_empty(JNIEnv * env, jobject object, jint cursorWindowSize,
         jboolean localOnly)
 {
     uint8_t * data;
@@ -59,44 +59,38 @@
 
     window = new CursorWindow(cursorWindowSize);
     if (!window) {
-        jniThrowException(env, "java/lang/RuntimeException", "No memory for native window object");
-        return;
+        return 1;
     }
-
     if (!window->initBuffer(localOnly)) {
-        jniThrowException(env, "java/lang/RuntimeException",
-                "Memory couldn't be allocated for 1MB CursorWindow object.");
         delete window;
-        return;
+        return 1;
     }
 
-LOG_WINDOW("native_init_empty: window = %p", window);
+    LOG_WINDOW("native_init_empty: window = %p", window);
     SET_WINDOW(env, object, window);
+    return 0;
 }
 
-static void native_init_memory(JNIEnv * env, jobject object, jobject memObj)
+static jint native_init_memory(JNIEnv * env, jobject object, jobject memObj)
 {
     sp<IMemory> memory = interface_cast<IMemory>(ibinderForJavaObject(env, memObj));
     if (memory == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException", "Couldn't get native binder");
-        return;
+        return 1;
     }
 
     CursorWindow * window = new CursorWindow();
     if (!window) {
-        jniThrowException(env, "java/lang/RuntimeException",
-                "CursorWindow of size 1MB couldn't be created. No memory?");
-        return;
+        return 1;
     }
     if (!window->setMemory(memory)) {
-        jniThrowException(env, "java/lang/RuntimeException",
-                "Memory couldn't be initialized for 1MB CursorWindow object.");
         delete window;
-        return;
+        return 1;
     }
 
-LOG_WINDOW("native_init_memory: numRows = %d, numColumns = %d, window = %p", window->getNumRows(), window->getNumColumns(), window);
+    LOG_WINDOW("native_init_memory: numRows = %d, numColumns = %d, window = %p", window->getNumRows(), window->getNumColumns(), window);
     SET_WINDOW(env, object, window);
+    return 0;
 }
 
 static jobject native_getBinder(JNIEnv * env, jobject object)
@@ -615,8 +609,8 @@
 static JNINativeMethod sMethods[] =
 {
      /* name, signature, funcPtr */
-    {"native_init", "(IZ)V", (void *)native_init_empty},
-    {"native_init", "(Landroid/os/IBinder;)V", (void *)native_init_memory},
+    {"native_init", "(IZ)I", (void *)native_init_empty},
+    {"native_init", "(Landroid/os/IBinder;)I", (void *)native_init_memory},
     {"native_getBinder", "()Landroid/os/IBinder;", (void *)native_getBinder},
     {"native_clear", "()V", (void *)native_clear},
     {"close_native", "()V", (void *)native_close},
diff --git a/core/tests/coretests/src/android/database/DatabaseCursorTest.java b/core/tests/coretests/src/android/database/DatabaseCursorTest.java
index 0733229..d5b9ee6 100644
--- a/core/tests/coretests/src/android/database/DatabaseCursorTest.java
+++ b/core/tests/coretests/src/android/database/DatabaseCursorTest.java
@@ -37,6 +37,7 @@
 import android.util.Log;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Random;
 
@@ -581,4 +582,35 @@
         c.deactivate();
         c.requery();
     }
+    /**
+     * sometimes CursorWindow creation fails due to non-availability of memory create
+     * another CursorWindow object. One of the scenarios of its occurrence is when
+     * there are too many CursorWindow objects already opened by the process.
+     * This test is for that scenario.
+     */
+    @LargeTest
+    public void testCursorWindowFailureWhenTooManyCursorWindowsLeftOpen() {
+        mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
+        mDatabase.execSQL("INSERT INTO test values(1, 'test');");
+        int N = 1024;
+        ArrayList<Cursor> cursorList = new ArrayList<Cursor>();
+        // open many cursors until a failure occurs
+        for (int i = 0; i < N; i++) {
+            try {
+                Cursor cursor = mDatabase.rawQuery("select * from test", null);
+                cursor.getCount();
+                cursorList.add(cursor);
+            } catch (CursorWindowAllocationException e) {
+                // got the exception we wanted
+                break;
+            } catch (Exception e) {
+                fail("unexpected exception: " + e.getMessage());
+                e.printStackTrace();
+                break;
+            }
+        }
+        for (Cursor c : cursorList) {
+            c.close();
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/widget/TextViewWordLimitsTest.java b/core/tests/coretests/src/android/widget/TextViewWordLimitsTest.java
new file mode 100644
index 0000000..fbc9e1f
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/TextViewWordLimitsTest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package android.widget;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.text.InputType;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.SpannableString;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * TextViewPatchTest tests {@link TextView}'s definition of word. Finds and
+ * verifies word limits to be in strings containing different kinds of
+ * characters.
+ */
+public class TextViewWordLimitsTest extends AndroidTestCase {
+
+    TextView mTv = null;
+    Method mGetWordLimits, mSelectCurrentWord;
+    Field mContextMenuTriggeredByKey, mSelectionControllerEnabled;
+
+
+    /**
+     * Sets up common fields used in all test cases.
+     * @throws NoSuchFieldException
+     * @throws SecurityException
+     */
+    @Override
+    protected void setUp() throws NoSuchMethodException, SecurityException, NoSuchFieldException {
+        mTv = new TextView(getContext());
+        mTv.setInputType(InputType.TYPE_CLASS_TEXT);
+
+        mGetWordLimits = mTv.getClass().getDeclaredMethod("getWordLimitsAt",
+                new Class[] {int.class});
+        mGetWordLimits.setAccessible(true);
+
+        mSelectCurrentWord = mTv.getClass().getDeclaredMethod("selectCurrentWord", new Class[] {});
+        mSelectCurrentWord.setAccessible(true);
+
+        mContextMenuTriggeredByKey = mTv.getClass().getDeclaredField("mContextMenuTriggeredByKey");
+        mContextMenuTriggeredByKey.setAccessible(true);
+        mSelectionControllerEnabled = mTv.getClass().getDeclaredField("mSelectionControllerEnabled");
+        mSelectionControllerEnabled.setAccessible(true);
+    }
+
+    /**
+     * Calculate and verify word limits. Depends on the TextView implementation.
+     * Uses a private method and internal data representation.
+     *
+     * @param text         Text to select a word from
+     * @param pos          Position to expand word around
+     * @param correctStart Correct start position for the word
+     * @param correctEnd   Correct end position for the word
+     * @throws InvocationTargetException
+     * @throws IllegalAccessException
+     * @throws IllegalArgumentException
+     * @throws InvocationTargetException
+     * @throws IllegalAccessException
+     */
+    private void verifyWordLimits(String text, int pos, int correctStart, int correctEnd)
+    throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+        mTv.setText(text, TextView.BufferType.SPANNABLE);
+
+        long limits = (Long)mGetWordLimits.invoke(mTv, new Object[] {new Integer(pos)});
+        int actualStart = (int)(limits >>> 32);
+        int actualEnd = (int)(limits & 0x00000000FFFFFFFFL);
+        assertEquals(correctStart, actualStart);
+        assertEquals(correctEnd, actualEnd);
+    }
+
+
+    private void verifySelectCurrentWord(Spannable text, int selectionStart, int selectionEnd, int correctStart,
+            int correctEnd) throws InvocationTargetException, IllegalAccessException {
+        mTv.setText(text, TextView.BufferType.SPANNABLE);
+
+        Selection.setSelection((Spannable)mTv.getText(), selectionStart, selectionEnd);
+        mContextMenuTriggeredByKey.setBoolean(mTv, true);
+        mSelectionControllerEnabled.setBoolean(mTv, true);
+        mSelectCurrentWord.invoke(mTv);
+
+        assertEquals(correctStart, mTv.getSelectionStart());
+        assertEquals(correctEnd, mTv.getSelectionEnd());
+    }
+
+
+    /**
+     * Corner cases for string length.
+     */
+    @LargeTest
+    public void testLengths() throws Exception {
+        final String ONE_TWO = "one two";
+        final String EMPTY   = "";
+        final String TOOLONG = "ThisWordIsTooLongToBeDefinedAsAWordInTheSenseUsedInTextView";
+
+        // Select first word
+        verifyWordLimits(ONE_TWO, 0, 0, 3);
+        verifyWordLimits(ONE_TWO, 3, 0, 3);
+
+        // Select last word
+        verifyWordLimits(ONE_TWO, 4, 4, 7);
+        verifyWordLimits(ONE_TWO, 7, 4, 7);
+
+        // Empty string
+        verifyWordLimits(EMPTY, 0, -1, -1);
+
+        // Too long word
+        verifyWordLimits(TOOLONG, 0, -1, -1);
+    }
+
+    /**
+     * Unicode classes included.
+     */
+    @LargeTest
+    public void testIncludedClasses() throws Exception {
+        final String LOWER          = "njlj";
+        final String UPPER          = "NJLJ";
+        final String TITLECASE      = "\u01C8\u01CB\u01F2"; // Lj Nj Dz
+        final String OTHER          = "\u3042\u3044\u3046"; // Hiragana AIU
+        final String MODIFIER       = "\u02C6\u02CA\u02CB"; // Circumflex Acute Grave
+
+        // Each string contains a single valid word
+        verifyWordLimits(LOWER, 1, 0, 4);
+        verifyWordLimits(UPPER, 1, 0, 4);
+        verifyWordLimits(TITLECASE, 1, 0, 3);
+        verifyWordLimits(OTHER, 1, 0, 3);
+        verifyWordLimits(MODIFIER, 1, 0, 3);
+    }
+
+    /**
+     * Unicode classes included if combined with a letter.
+     */
+    @LargeTest
+    public void testPartlyIncluded() throws Exception {
+        final String NUMBER           = "123";
+        final String NUMBER_LOWER     = "1st";
+        final String APOSTROPHE       = "''";
+        final String APOSTROPHE_LOWER = "'Android's'";
+
+        // Pure decimal number is ignored
+        verifyWordLimits(NUMBER, 1, -1, -1);
+
+        // Number with letter is valid
+        verifyWordLimits(NUMBER_LOWER, 1, 0, 3);
+
+        // Stand apostrophes are ignore
+        verifyWordLimits(APOSTROPHE, 1, -1, -1);
+
+        // Apostrophes are accepted if they are a part of a word
+        verifyWordLimits(APOSTROPHE_LOWER, 1, 0, 11);
+    }
+
+    /**
+     * Unicode classes included if combined with a letter.
+     */
+    @LargeTest
+    public void testFinalSeparator() throws Exception {
+        final String PUNCTUATION = "abc, def.";
+
+        // Starting from the comma
+        verifyWordLimits(PUNCTUATION, 3, 0, 3);
+        verifyWordLimits(PUNCTUATION, 4, 0, 4);
+
+        // Starting from the final period
+        verifyWordLimits(PUNCTUATION, 8, 5, 8);
+        verifyWordLimits(PUNCTUATION, 9, 5, 9);
+    }
+
+    /**
+     * Unicode classes other than listed in testIncludedClasses and
+     * testPartlyIncluded act as word separators.
+     */
+    @LargeTest
+    public void testNotIncluded() throws Exception {
+        // Selection of character classes excluded
+        final String MARK_NONSPACING        = "a\u030A";       // a Combining ring above
+        final String PUNCTUATION_OPEN_CLOSE = "(c)";           // Parenthesis
+        final String PUNCTUATION_DASH       = "non-fiction";   // Hyphen
+        final String PUNCTUATION_OTHER      = "b&b";           // Ampersand
+        final String SYMBOL_OTHER           = "Android\u00AE"; // Registered
+        final String SEPARATOR_SPACE        = "one two";       // Space
+
+        // "a"
+        verifyWordLimits(MARK_NONSPACING, 1, 0, 1);
+
+        // "c"
+        verifyWordLimits(PUNCTUATION_OPEN_CLOSE, 1, 1, 2);
+
+        // "non-"
+        verifyWordLimits(PUNCTUATION_DASH, 3, 0, 3);
+        verifyWordLimits(PUNCTUATION_DASH, 4, 4, 11);
+
+        // "b"
+        verifyWordLimits(PUNCTUATION_OTHER, 0, 0, 1);
+        verifyWordLimits(PUNCTUATION_OTHER, 1, 0, 1);
+        verifyWordLimits(PUNCTUATION_OTHER, 2, 0, 3); // & is considered a punctuation sign.
+        verifyWordLimits(PUNCTUATION_OTHER, 3, 2, 3);
+
+        // "Android"
+        verifyWordLimits(SYMBOL_OTHER, 7, 0, 7);
+        verifyWordLimits(SYMBOL_OTHER, 8, -1, -1);
+
+        // "one"
+        verifyWordLimits(SEPARATOR_SPACE, 1, 0, 3);
+    }
+
+    /**
+     * Surrogate characters are treated as their code points.
+     */
+    @LargeTest
+    public void testSurrogate() throws Exception {
+        final String SURROGATE_LETTER   = "\uD800\uDC00\uD800\uDC01\uD800\uDC02"; // Linear B AEI
+        final String SURROGATE_SYMBOL   = "\uD83D\uDE01\uD83D\uDE02\uD83D\uDE03"; // Three smileys
+
+        // Letter Other is included even when coded as surrogate pairs
+        verifyWordLimits(SURROGATE_LETTER, 1, -1, -1);
+        verifyWordLimits(SURROGATE_LETTER, 2, -1, -1);
+
+        // Not included classes are ignored even when coded as surrogate pairs
+        verifyWordLimits(SURROGATE_SYMBOL, 1, -1, -1);
+        verifyWordLimits(SURROGATE_SYMBOL, 2, -1, -1);
+    }
+
+    /**
+     * Selection is used if present and valid word.
+     */
+    @LargeTest
+    public void testSelectCurrentWord() throws Exception {
+        SpannableString textLower       = new SpannableString("first second");
+        SpannableString textOther       = new SpannableString("\u3042\3044\3046"); // Hiragana AIU
+        SpannableString textDash        = new SpannableString("non-fiction");      // Hyphen
+        SpannableString textPunctOther  = new SpannableString("b&b");              // Ampersand
+        SpannableString textSymbolOther = new SpannableString("Android\u00AE");    // Registered
+
+        // Valid selection - Letter, Lower
+        verifySelectCurrentWord(textLower, 2, 5, 0, 5);
+
+        // Adding the space spreads to the second word
+        verifySelectCurrentWord(textLower, 2, 6, 0, 12);
+
+        // Valid selection -- Letter, Other
+        verifySelectCurrentWord(textOther, 1, 2, 0, 5);
+
+        // Zero-width selection is interpreted as a cursor and the selection is ignored
+        verifySelectCurrentWord(textLower, 2, 2, 0, 5);
+
+        // Hyphen is part of selection
+        verifySelectCurrentWord(textDash, 2, 5, 0, 11);
+
+        // Ampersand part of selection or not
+        verifySelectCurrentWord(textPunctOther, 1, 2, 0, 3);
+        verifySelectCurrentWord(textPunctOther, 1, 3, 0, 3);
+
+        // (R) part of the selection
+        verifySelectCurrentWord(textSymbolOther, 2, 7, 0, 7);
+        verifySelectCurrentWord(textSymbolOther, 2, 8, 0, 8);
+    }
+}
diff --git a/graphics/java/android/graphics/drawable/TransitionDrawable.java b/graphics/java/android/graphics/drawable/TransitionDrawable.java
index 4470356..9a3ca40 100644
--- a/graphics/java/android/graphics/drawable/TransitionDrawable.java
+++ b/graphics/java/android/graphics/drawable/TransitionDrawable.java
@@ -137,7 +137,7 @@
         final long time = SystemClock.uptimeMillis();
         // Animation is over
         if (time - mStartTimeMillis > mDuration) {
-            if (mAlpha == 0) {
+            if (mTo == 0) {
                 mFrom = 0;
                 mTo = 255;
                 mAlpha = 0;
diff --git a/include/media/mediaplayer.h b/include/media/mediaplayer.h
index 88b0c3e..178f0db 100644
--- a/include/media/mediaplayer.h
+++ b/include/media/mediaplayer.h
@@ -36,6 +36,7 @@
     MEDIA_BUFFERING_UPDATE  = 3,
     MEDIA_SEEK_COMPLETE     = 4,
     MEDIA_SET_VIDEO_SIZE    = 5,
+    MEDIA_RESET_COMPLETE    = 6,  // not visible on java side
     MEDIA_ERROR             = 100,
     MEDIA_INFO              = 200,
 };
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index c92fc23..a3c3f09 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -262,14 +262,6 @@
         return getFrameAtTime(-1, OPTION_CLOSEST_SYNC);
     }
 
-    /**
-     * FIXME
-     * To be removed and replaced by getFrameAt().
-     */
-    public Bitmap captureFrame() {
-        return _getFrameAtTime(-1, OPTION_CLOSEST_SYNC);
-    }
-
     private native Bitmap _getFrameAtTime(long timeUs, int option);
 
     
diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
index c656e69..29e4971 100644
--- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
@@ -87,7 +87,7 @@
             break;
         } else if (n < 0) {
             LOGI("input data EOS reached.");
-            mTSParser->signalEOS(ERROR_END_OF_STREAM);
+            mTSParser->signalEOS(n);
             mEOS = true;
             break;
         } else {
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index 24efa35..9a8cff3 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -45,8 +45,11 @@
     : mAudioEOS(false),
       mVideoEOS(false),
       mScanSourcesPending(false),
+      mScanSourcesGeneration(0),
       mFlushingAudio(NONE),
-      mFlushingVideo(NONE) {
+      mFlushingVideo(NONE),
+      mResetInProgress(false),
+      mResetPostponed(false) {
 }
 
 NuPlayer::~NuPlayer() {
@@ -87,18 +90,22 @@
     (new AMessage(kWhatStart, id()))->post();
 }
 
+void NuPlayer::resetAsync() {
+    (new AMessage(kWhatReset, id()))->post();
+}
+
 // static
-bool NuPlayer::IsFlushingState(FlushStatus state, bool *formatChange) {
+bool NuPlayer::IsFlushingState(FlushStatus state, bool *needShutdown) {
     switch (state) {
         case FLUSHING_DECODER:
-            if (formatChange != NULL) {
-                *formatChange = false;
+            if (needShutdown != NULL) {
+                *needShutdown = false;
             }
             return true;
 
-        case FLUSHING_DECODER_FORMATCHANGE:
-            if (formatChange != NULL) {
-                *formatChange = true;
+        case FLUSHING_DECODER_SHUTDOWN:
+            if (needShutdown != NULL) {
+                *needShutdown = true;
             }
             return true;
 
@@ -111,7 +118,7 @@
     switch (msg->what()) {
         case kWhatSetDataSource:
         {
-            LOGI("kWhatSetDataSource");
+            LOGV("kWhatSetDataSource");
 
             CHECK(mSource == NULL);
 
@@ -124,7 +131,7 @@
 
         case kWhatSetVideoSurface:
         {
-            LOGI("kWhatSetVideoSurface");
+            LOGV("kWhatSetVideoSurface");
 
             sp<RefBase> obj;
             CHECK(msg->findObject("surface", &obj));
@@ -135,7 +142,7 @@
 
         case kWhatSetAudioSink:
         {
-            LOGI("kWhatSetAudioSink");
+            LOGV("kWhatSetAudioSink");
 
             sp<RefBase> obj;
             CHECK(msg->findObject("sink", &obj));
@@ -146,6 +153,9 @@
 
         case kWhatStart:
         {
+            mAudioEOS = false;
+            mVideoEOS = false;
+
             mSource->start();
 
             mRenderer = new Renderer(
@@ -154,13 +164,19 @@
 
             looper()->registerHandler(mRenderer);
 
-            (new AMessage(kWhatScanSources, id()))->post();
-            mScanSourcesPending = true;
+            postScanSources();
             break;
         }
 
         case kWhatScanSources:
         {
+            int32_t generation;
+            CHECK(msg->findInt32("generation", &generation));
+            if (generation != mScanSourcesGeneration) {
+                // Drop obsolete msg.
+                break;
+            }
+
             mScanSourcesPending = false;
 
             instantiateDecoder(false, &mVideoDecoder);
@@ -170,6 +186,11 @@
             }
 
             if (!mSource->feedMoreTSData()) {
+                if (mAudioDecoder == NULL && mVideoDecoder == NULL) {
+                    // We're not currently decoding anything (no audio or
+                    // video tracks found) and we just ran out of input data.
+                    notifyListener(MEDIA_PLAYBACK_COMPLETE, 0, 0);
+                }
                 break;
             }
 
@@ -203,20 +224,20 @@
             } else if (what == ACodec::kWhatEOS) {
                 mRenderer->queueEOS(audio, ERROR_END_OF_STREAM);
             } else if (what == ACodec::kWhatFlushCompleted) {
-                bool formatChange;
+                bool needShutdown;
 
                 if (audio) {
-                    CHECK(IsFlushingState(mFlushingAudio, &formatChange));
+                    CHECK(IsFlushingState(mFlushingAudio, &needShutdown));
                     mFlushingAudio = FLUSHED;
                 } else {
-                    CHECK(IsFlushingState(mFlushingVideo, &formatChange));
+                    CHECK(IsFlushingState(mFlushingVideo, &needShutdown));
                     mFlushingVideo = FLUSHED;
                 }
 
-                LOGI("decoder %s flush completed", audio ? "audio" : "video");
+                LOGV("decoder %s flush completed", audio ? "audio" : "video");
 
-                if (formatChange) {
-                    LOGI("initiating %s decoder shutdown",
+                if (needShutdown) {
+                    LOGV("initiating %s decoder shutdown",
                          audio ? "audio" : "video");
 
                     (audio ? mAudioDecoder : mVideoDecoder)->initiateShutdown();
@@ -238,7 +259,7 @@
                 int32_t sampleRate;
                 CHECK(codecRequest->findInt32("sample-rate", &sampleRate));
 
-                LOGI("Audio output format changed to %d Hz, %d channels",
+                LOGV("Audio output format changed to %d Hz, %d channels",
                      sampleRate, numChannels);
 
                 mAudioSink->close();
@@ -247,7 +268,7 @@
 
                 mRenderer->signalAudioSinkChanged();
             } else if (what == ACodec::kWhatShutdownCompleted) {
-                LOGI("%s shutdown completed", audio ? "audio" : "video");
+                LOGV("%s shutdown completed", audio ? "audio" : "video");
                 if (audio) {
                     mAudioDecoder.clear();
 
@@ -285,7 +306,7 @@
                     mVideoEOS = true;
                 }
 
-                LOGI("reached %s EOS", audio ? "audio" : "video");
+                LOGV("reached %s EOS", audio ? "audio" : "video");
 
                 if ((mAudioEOS || mAudioDecoder == NULL)
                         && (mVideoEOS || mVideoDecoder == NULL)) {
@@ -297,7 +318,7 @@
                 int32_t audio;
                 CHECK(msg->findInt32("audio", &audio));
 
-                LOGI("renderer %s flush completed.", audio ? "audio" : "video");
+                LOGV("renderer %s flush completed.", audio ? "audio" : "video");
             }
             break;
         }
@@ -307,6 +328,37 @@
             break;
         }
 
+        case kWhatReset:
+        {
+            LOGV("kWhatReset");
+
+            if (mFlushingAudio != NONE || mFlushingVideo != NONE) {
+                // We're currently flushing, postpone the reset until that's
+                // completed.
+
+                LOGV("postponing reset");
+
+                mResetPostponed = true;
+                break;
+            }
+
+            if (mAudioDecoder == NULL && mVideoDecoder == NULL) {
+                finishReset();
+                break;
+            }
+
+            if (mAudioDecoder != NULL) {
+                flushDecoder(true /* audio */, true /* needShutdown */);
+            }
+
+            if (mVideoDecoder != NULL) {
+                flushDecoder(false /* audio */, true /* needShutdown */);
+            }
+
+            mResetInProgress = true;
+            break;
+        }
+
         default:
             TRESPASS();
             break;
@@ -322,7 +374,7 @@
         return;
     }
 
-    LOGI("both audio and video are flushed now.");
+    LOGV("both audio and video are flushed now.");
 
     mRenderer->signalTimeDiscontinuity();
 
@@ -343,12 +395,41 @@
     mFlushingAudio = NONE;
     mFlushingVideo = NONE;
 
-    if (scanSourcesAgain && !mScanSourcesPending) {
-        mScanSourcesPending = true;
-        (new AMessage(kWhatScanSources, id()))->post();
+    if (mResetInProgress) {
+        LOGV("reset completed");
+
+        mResetInProgress = false;
+        finishReset();
+    } else if (mResetPostponed) {
+        (new AMessage(kWhatReset, id()))->post();
+        mResetPostponed = false;
+    } else if (scanSourcesAgain) {
+        postScanSources();
     }
 }
 
+void NuPlayer::finishReset() {
+    CHECK(mAudioDecoder == NULL);
+    CHECK(mVideoDecoder == NULL);
+
+    mRenderer.clear();
+    mSource.clear();
+
+    notifyListener(MEDIA_RESET_COMPLETE, 0, 0);
+}
+
+void NuPlayer::postScanSources() {
+    if (mScanSourcesPending) {
+        return;
+    }
+
+    sp<AMessage> msg = new AMessage(kWhatScanSources, id());
+    msg->setInt32("generation", mScanSourcesGeneration);
+    msg->post();
+
+    mScanSourcesPending = true;
+}
+
 status_t NuPlayer::instantiateDecoder(bool audio, sp<Decoder> *decoder) {
     if (*decoder != NULL) {
         return OK;
@@ -396,37 +477,10 @@
             bool formatChange =
                 type == ATSParser::DISCONTINUITY_FORMATCHANGE;
 
-            LOGI("%s discontinuity (formatChange=%d)",
+            LOGV("%s discontinuity (formatChange=%d)",
                  audio ? "audio" : "video", formatChange);
 
-            (audio ? mAudioDecoder : mVideoDecoder)->signalFlush();
-            mRenderer->flush(audio);
-
-            if (audio) {
-                CHECK(mFlushingAudio == NONE
-                        || mFlushingAudio == AWAITING_DISCONTINUITY);
-
-                mFlushingAudio = formatChange
-                    ? FLUSHING_DECODER_FORMATCHANGE : FLUSHING_DECODER;
-
-                if (mFlushingVideo == NONE) {
-                    mFlushingVideo = (mVideoDecoder != NULL)
-                        ? AWAITING_DISCONTINUITY
-                        : FLUSHED;
-                }
-            } else {
-                CHECK(mFlushingVideo == NONE
-                        || mFlushingVideo == AWAITING_DISCONTINUITY);
-
-                mFlushingVideo = formatChange
-                    ? FLUSHING_DECODER_FORMATCHANGE : FLUSHING_DECODER;
-
-                if (mFlushingAudio == NONE) {
-                    mFlushingAudio = (mAudioDecoder != NULL)
-                        ? AWAITING_DISCONTINUITY
-                        : FLUSHED;
-                }
-            }
+            flushDecoder(audio, formatChange);
         }
 
         reply->setInt32("err", err);
@@ -439,7 +493,7 @@
 #if 0
     int64_t mediaTimeUs;
     CHECK(accessUnit->meta()->findInt64("timeUs", &mediaTimeUs));
-    LOGI("feeding %s input buffer at media time %.2f secs",
+    LOGV("feeding %s input buffer at media time %.2f secs",
          audio ? "audio" : "video",
          mediaTimeUs / 1E6);
 #endif
@@ -478,4 +532,39 @@
     listener->sendEvent(msg, ext1, ext2);
 }
 
+void NuPlayer::flushDecoder(bool audio, bool needShutdown) {
+    // Make sure we don't continue to scan sources until we finish flushing.
+    ++mScanSourcesGeneration;
+
+    (audio ? mAudioDecoder : mVideoDecoder)->signalFlush();
+    mRenderer->flush(audio);
+
+    FlushStatus newStatus =
+        needShutdown ? FLUSHING_DECODER_SHUTDOWN : FLUSHING_DECODER;
+
+    if (audio) {
+        CHECK(mFlushingAudio == NONE
+                || mFlushingAudio == AWAITING_DISCONTINUITY);
+
+        mFlushingAudio = newStatus;
+
+        if (mFlushingVideo == NONE) {
+            mFlushingVideo = (mVideoDecoder != NULL)
+                ? AWAITING_DISCONTINUITY
+                : FLUSHED;
+        }
+    } else {
+        CHECK(mFlushingVideo == NONE
+                || mFlushingVideo == AWAITING_DISCONTINUITY);
+
+        mFlushingVideo = newStatus;
+
+        if (mFlushingAudio == NONE) {
+            mFlushingAudio = (mAudioDecoder != NULL)
+                ? AWAITING_DISCONTINUITY
+                : FLUSHED;
+        }
+    }
+}
+
 }  // namespace android
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h
index 172a962..1cf2f60 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h
@@ -40,6 +40,10 @@
     void setAudioSink(const sp<MediaPlayerBase::AudioSink> &sink);
     void start();
 
+    // Will notify the listener that reset() has completed
+    // with code MEDIA_RESET_COMPLETE.
+    void resetAsync();
+
 protected:
     virtual ~NuPlayer();
 
@@ -63,6 +67,7 @@
         kWhatVideoNotify,
         kWhatAudioNotify,
         kWhatRendererNotify,
+        kWhatReset,
     };
 
     wp<MediaPlayerBase> mListener;
@@ -73,17 +78,17 @@
     sp<Decoder> mAudioDecoder;
     sp<Renderer> mRenderer;
 
-    bool mEOS;
     bool mAudioEOS;
     bool mVideoEOS;
 
     bool mScanSourcesPending;
+    int32_t mScanSourcesGeneration;
 
     enum FlushStatus {
         NONE,
         AWAITING_DISCONTINUITY,
         FLUSHING_DECODER,
-        FLUSHING_DECODER_FORMATCHANGE,
+        FLUSHING_DECODER_SHUTDOWN,
         SHUTTING_DOWN_DECODER,
         FLUSHED,
         SHUT_DOWN,
@@ -91,6 +96,8 @@
 
     FlushStatus mFlushingAudio;
     FlushStatus mFlushingVideo;
+    bool mResetInProgress;
+    bool mResetPostponed;
 
     status_t instantiateDecoder(bool audio, sp<Decoder> *decoder);
 
@@ -101,7 +108,12 @@
 
     void finishFlushIfPossible();
 
-    static bool IsFlushingState(FlushStatus state, bool *formatChange = NULL);
+    void flushDecoder(bool audio, bool needShutdown);
+
+    static bool IsFlushingState(FlushStatus state, bool *needShutdown = NULL);
+
+    void finishReset();
+    void postScanSources();
 
     DISALLOW_EVIL_CONSTRUCTORS(NuPlayer);
 };
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
index d21d4ff..4988d24 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
@@ -22,12 +22,14 @@
 
 #include "NuPlayer.h"
 
+#include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/ALooper.h>
 
 namespace android {
 
 NuPlayerDriver::NuPlayerDriver()
-    : mLooper(new ALooper),
+    : mResetInProgress(false),
+      mLooper(new ALooper),
       mPlayer(false) {
     mLooper->setName("NuPlayerDriver Looper");
 
@@ -121,6 +123,15 @@
 }
 
 status_t NuPlayerDriver::reset() {
+    Mutex::Autolock autoLock(mLock);
+    mResetInProgress = true;
+
+    mPlayer->resetAsync();
+
+    while (mResetInProgress) {
+        mCondition.wait(mLock);
+    }
+
     return OK;
 }
 
@@ -145,4 +156,16 @@
     return INVALID_OPERATION;
 }
 
+void NuPlayerDriver::sendEvent(int msg, int ext1, int ext2) {
+    if (msg != MEDIA_RESET_COMPLETE) {
+        MediaPlayerInterface::sendEvent(msg, ext1, ext2);
+        return;
+    }
+
+    Mutex::Autolock autoLock(mLock);
+    CHECK(mResetInProgress);
+    mResetInProgress = false;
+    mCondition.broadcast();
+}
+
 }  // namespace android
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
index 44ae3bf..f153af4 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
@@ -54,10 +54,16 @@
     virtual status_t getMetadata(
             const media::Metadata::Filter& ids, Parcel *records);
 
+    virtual void sendEvent(int msg, int ext1 = 0, int ext2 = 0);
+
 protected:
     virtual ~NuPlayerDriver();
 
 private:
+    Mutex mLock;
+    Condition mCondition;
+    bool mResetInProgress;
+
     sp<ALooper> mLooper;
     sp<NuPlayer> mPlayer;
     bool mPlaying;
diff --git a/media/libstagefright/StagefrightMetadataRetriever.cpp b/media/libstagefright/StagefrightMetadataRetriever.cpp
index ee3eefc..6331a63 100644
--- a/media/libstagefright/StagefrightMetadataRetriever.cpp
+++ b/media/libstagefright/StagefrightMetadataRetriever.cpp
@@ -260,7 +260,7 @@
 
     LOGV("getFrameAtTime: %lld us option: %d", timeUs, option);
     if (0 == (mMode & METADATA_MODE_FRAME_CAPTURE_ONLY)) {
-        LOGV("captureFrame disabled by mode (0x%08x)", mMode);
+        LOGV("getFrameAtTime disabled by mode (0x%08x)", mMode);
 
         return NULL;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java
index bd9bdb2..612427b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java
@@ -257,20 +257,23 @@
 
     private Bitmap compositeBitmap(Bitmap background, Bitmap thumbnail) {
         Bitmap outBitmap = background.copy(background.getConfig(), true);
-        Canvas canvas = new Canvas(outBitmap);
-        Paint paint = new Paint();
-        paint.setAntiAlias(true);
-        paint.setFilterBitmap(true);
-        paint.setAlpha(255);
-        final int srcWidth = thumbnail.getWidth();
-        final int height = thumbnail.getHeight();
-        final int srcHeight = srcWidth > height ? height : (height - height * srcWidth / height);
-        canvas.drawBitmap(thumbnail,
-                new Rect(0, 0, srcWidth-1, srcHeight-1),
-                new RectF(GLOW_PADDING,
-                        GLOW_PADDING - 4.0f,
-                        outBitmap.getWidth() - GLOW_PADDING + 2.0f,
-                        outBitmap.getHeight() - GLOW_PADDING + 3.0f), paint);
+        if (thumbnail != null) {
+            Canvas canvas = new Canvas(outBitmap);
+            Paint paint = new Paint();
+            paint.setAntiAlias(true);
+            paint.setFilterBitmap(true);
+            paint.setAlpha(255);
+            final int srcWidth = thumbnail.getWidth();
+            final int height = thumbnail.getHeight();
+            final int srcHeight = srcWidth > height ? height
+                    : (height - height * srcWidth / height);
+            canvas.drawBitmap(thumbnail,
+                    new Rect(0, 0, srcWidth-1, srcHeight-1),
+                    new RectF(GLOW_PADDING,
+                            GLOW_PADDING - 4.0f,
+                            outBitmap.getWidth() - GLOW_PADDING + 2.0f,
+                            outBitmap.getHeight() - GLOW_PADDING + 3.0f), paint);
+        }
         return outBitmap;
     }