Merge "Better support for rtsp (normal play-)time display. Better seek support, timeout if no packets arrive for too long." into gingerbread
diff --git a/api/current.xml b/api/current.xml
index 215a864..47a8472 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -78424,7 +78424,7 @@
  type="float"
  transient="false"
  volatile="false"
- value="0.0010f"
+ value="0.001f"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -224842,7 +224842,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="t" type="T">
+<parameter name="arg0" type="T">
 </parameter>
 </method>
 </interface>
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a3a8f09..773ff7c 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1162,6 +1162,7 @@
      */
     protected void onPause() {
         mCalled = true;
+        QueuedWork.waitToFinish();
     }
 
     /**
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index c07e3d3..084f637 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -152,7 +152,7 @@
             = new ArrayList<Application>();
     // set of instantiated backup agents, keyed by package name
     final HashMap<String, BackupAgent> mBackupAgents = new HashMap<String, BackupAgent>();
-    static final ThreadLocal sThreadLocal = new ThreadLocal();
+    static final ThreadLocal<ActivityThread> sThreadLocal = new ThreadLocal();
     Instrumentation mInstrumentation;
     String mInstrumentationAppDir = null;
     String mInstrumentationAppPackage = null;
@@ -186,6 +186,8 @@
     final GcIdler mGcIdler = new GcIdler();
     boolean mGcIdlerScheduled = false;
 
+    static Handler sMainThreadHandler;  // set once in main()
+
     private static final class ActivityClientRecord {
         IBinder token;
         int ident;
@@ -1111,7 +1113,7 @@
     }
 
     public static final ActivityThread currentActivityThread() {
-        return (ActivityThread)sThreadLocal.get();
+        return sThreadLocal.get();
     }
 
     public static final String currentPackageName() {
@@ -1780,6 +1782,8 @@
             }
         }
 
+        QueuedWork.waitToFinish();
+
         try {
             if (data.sync) {
                 if (DEBUG_BROADCAST) Slog.i(TAG,
@@ -2007,6 +2011,9 @@
                     data.args.setExtrasClassLoader(s.getClassLoader());
                 }
                 int res = s.onStartCommand(data.args, data.flags, data.startId);
+
+                QueuedWork.waitToFinish();
+
                 try {
                     ActivityManagerNative.getDefault().serviceDoneExecuting(
                             data.token, 1, data.startId, res);
@@ -2035,6 +2042,9 @@
                     final String who = s.getClassName();
                     ((ContextImpl) context).scheduleFinalCleanup(who, "Service");
                 }
+
+                QueuedWork.waitToFinish();
+
                 try {
                     ActivityManagerNative.getDefault().serviceDoneExecuting(
                             token, 0, 0, 0);
@@ -3598,6 +3608,9 @@
         Process.setArgV0("<pre-initialized>");
 
         Looper.prepareMainLooper();
+        if (sMainThreadHandler == null) {
+            sMainThreadHandler = new Handler();
+        }
 
         ActivityThread thread = new ActivityThread();
         thread.attach(false);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 7b35e7f..0cd1f3a 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -119,9 +119,12 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.WeakHashMap;
-import java.util.Map.Entry;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 class ReceiverRestrictedContext extends ContextWrapper {
     ReceiverRestrictedContext(Context base) {
@@ -170,8 +173,8 @@
     private static ThrottleManager sThrottleManager;
     private static WifiManager sWifiManager;
     private static LocationManager sLocationManager;
-    private static final HashMap<File, SharedPreferencesImpl> sSharedPrefs =
-            new HashMap<File, SharedPreferencesImpl>();
+    private static final HashMap<String, SharedPreferencesImpl> sSharedPrefs =
+            new HashMap<String, SharedPreferencesImpl>();
 
     private AudioManager mAudioManager;
     /*package*/ LoadedApk mPackageInfo;
@@ -210,7 +213,7 @@
     private File mCacheDir;
     private File mExternalFilesDir;
     private File mExternalCacheDir;
-    
+
     private static long sInstanceCount = 0;
 
     private static final String[] EMPTY_FILE_LIST = {};
@@ -335,15 +338,15 @@
     @Override
     public SharedPreferences getSharedPreferences(String name, int mode) {
         SharedPreferencesImpl sp;
-        File f = getSharedPrefsFile(name);
         synchronized (sSharedPrefs) {
-            sp = sSharedPrefs.get(f);
+            sp = sSharedPrefs.get(name);
             if (sp != null && !sp.hasFileChanged()) {
                 //Log.i(TAG, "Returning existing prefs " + name + ": " + sp);
                 return sp;
             }
         }
-        
+        File f = getSharedPrefsFile(name);
+
         FileInputStream str = null;
         File backup = makeBackupFile(f);
         if (backup.exists()) {
@@ -355,7 +358,7 @@
         if (f.exists() && !f.canRead()) {
             Log.w(TAG, "Attempt to read preferences file " + f + " without permission");
         }
-        
+
         Map map = null;
         if (f.exists() && f.canRead()) {
             try {
@@ -376,10 +379,10 @@
                 //Log.i(TAG, "Updating existing prefs " + name + " " + sp + ": " + map);
                 sp.replace(map);
             } else {
-                sp = sSharedPrefs.get(f);
+                sp = sSharedPrefs.get(name);
                 if (sp == null) {
                     sp = new SharedPreferencesImpl(f, mode, map);
-                    sSharedPrefs.put(f, sp);
+                    sSharedPrefs.put(name, sp);
                 }
             }
             return sp;
@@ -2698,10 +2701,12 @@
         private final File mFile;
         private final File mBackupFile;
         private final int mMode;
-        private Map mMap;
-        private final FileStatus mFileStatus = new FileStatus();
-        private long mTimestamp;
 
+        private Map<String, Object> mMap;  // guarded by 'this'
+        private long mTimestamp;  // guarded by 'this'
+        private int mDiskWritesInFlight = 0;  // guarded by 'this'
+
+        private final Object mWritingToDiskLock = new Object();
         private static final Object mContent = new Object();
         private WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners;
 
@@ -2710,22 +2715,24 @@
             mFile = file;
             mBackupFile = makeBackupFile(file);
             mMode = mode;
-            mMap = initialContents != null ? initialContents : new HashMap();
-            if (FileUtils.getFileStatus(file.getPath(), mFileStatus)) {
-                mTimestamp = mFileStatus.mtime;
+            mMap = initialContents != null ? initialContents : new HashMap<String, Object>();
+            FileStatus stat = new FileStatus();
+            if (FileUtils.getFileStatus(file.getPath(), stat)) {
+                mTimestamp = stat.mtime;
             }
             mListeners = new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
         }
 
         public boolean hasFileChanged() {
+            FileStatus stat = new FileStatus();
+            if (!FileUtils.getFileStatus(mFile.getPath(), stat)) {
+                return true;
+            }
             synchronized (this) {
-                if (!FileUtils.getFileStatus(mFile.getPath(), mFileStatus)) {
-                    return true;
-                }
-                return mTimestamp != mFileStatus.mtime;
+                return mTimestamp != stat.mtime;
             }
         }
-        
+
         public void replace(Map newContents) {
             if (newContents != null) {
                 synchronized (this) {
@@ -2733,7 +2740,7 @@
                 }
             }
         }
-        
+
         public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
             synchronized(this) {
                 mListeners.put(listener, mContent);
@@ -2749,7 +2756,7 @@
         public Map<String, ?> getAll() {
             synchronized(this) {
                 //noinspection unchecked
-                return new HashMap(mMap);
+                return new HashMap<String, Object>(mMap);
             }
         }
 
@@ -2768,7 +2775,7 @@
         }
         public long getLong(String key, long defValue) {
             synchronized (this) {
-                Long v = (Long) mMap.get(key);
+                Long v = (Long)mMap.get(key);
                 return v != null ? v : defValue;
             }
         }
@@ -2791,10 +2798,31 @@
             }
         }
 
+        public Editor edit() {
+            return new EditorImpl();
+        }
+
+        // Return value from EditorImpl#commitToMemory()
+        private static class MemoryCommitResult {
+            public boolean changesMade;  // any keys different?
+            public List<String> keysModified;  // may be null
+            public Set<OnSharedPreferenceChangeListener> listeners;  // may be null
+            public Map<?, ?> mapToWriteToDisk;
+            public final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
+            public volatile boolean writeToDiskResult = false;
+
+            public void setDiskWriteResult(boolean result) {
+                writeToDiskResult = result;
+                writtenToDiskLatch.countDown();
+            }
+        }
+
         public final class EditorImpl implements Editor {
             private final Map<String, Object> mModified = Maps.newHashMap();
             private boolean mClear = false;
 
+            private AtomicBoolean mCommitInFlight = new AtomicBoolean(false);
+
             public Editor putString(String key, String value) {
                 synchronized (this) {
                     mModified.put(key, value);
@@ -2841,30 +2869,67 @@
             }
 
             public void startCommit() {
-                // TODO: implement
-                commit();
+                if (!mCommitInFlight.compareAndSet(false, true)) {
+                    throw new IllegalStateException("can't call startCommit() twice");
+                }
+
+                final MemoryCommitResult mcr = commitToMemory();
+                final Runnable awaitCommit = new Runnable() {
+                        public void run() {
+                            try {
+                                mcr.writtenToDiskLatch.await();
+                            } catch (InterruptedException ignored) {
+                            }
+                        }
+                    };
+
+                QueuedWork.add(awaitCommit);
+
+                Runnable postWriteRunnable = new Runnable() {
+                        public void run() {
+                            awaitCommit.run();
+                            mCommitInFlight.set(false);
+                            QueuedWork.remove(awaitCommit);
+                        }
+                    };
+
+                SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
+
+                // Okay to notify the listeners before it's hit disk
+                // because the listeners should always get the same
+                // SharedPreferences instance back, which has the
+                // changes reflected in memory.
+                notifyListeners(mcr);
             }
 
-            public boolean commit() {
-                boolean returnValue;
-
-                boolean hasListeners;
-                boolean changesMade = false;
-                List<String> keysModified = null;
-                Set<OnSharedPreferenceChangeListener> listeners = null;
-
+            // Returns true if any changes were made
+            private MemoryCommitResult commitToMemory() {
+                MemoryCommitResult mcr = new MemoryCommitResult();
                 synchronized (SharedPreferencesImpl.this) {
-                    hasListeners = mListeners.size() > 0;
+                    // We optimistically don't make a deep copy until
+                    // a memory commit comes in when we're already
+                    // writing to disk.
+                    if (mDiskWritesInFlight > 0) {
+                        // We can't modify our mMap as a currently
+                        // in-flight write owns it.  Clone it before
+                        // modifying it.
+                        // noinspection unchecked
+                        mMap = new HashMap<String, Object>(mMap);
+                    }
+                    mcr.mapToWriteToDisk = mMap;
+                    mDiskWritesInFlight++;
+
+                    boolean hasListeners = mListeners.size() > 0;
                     if (hasListeners) {
-                        keysModified = new ArrayList<String>();
-                        listeners =
-                                new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
+                        mcr.keysModified = new ArrayList<String>();
+                        mcr.listeners =
+                            new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
                     }
 
                     synchronized (this) {
                         if (mClear) {
                             if (!mMap.isEmpty()) {
-                                changesMade = true;
+                                mcr.changesMade = true;
                                 mMap.clear();
                             }
                             mClear = false;
@@ -2874,53 +2939,122 @@
                             String k = e.getKey();
                             Object v = e.getValue();
                             if (v == this) {  // magic value for a removal mutation
-                                if (mMap.containsKey(k)) {
-                                    mMap.remove(k);
-                                    changesMade = true;
+                                if (!mMap.containsKey(k)) {
+                                    continue;
                                 }
+                                mMap.remove(k);
                             } else {
                                 boolean isSame = false;
                                 if (mMap.containsKey(k)) {
                                     Object existingValue = mMap.get(k);
-                                    isSame = existingValue != null && existingValue.equals(v);
+                                    if (existingValue != null && existingValue.equals(v)) {
+                                        continue;
+                                    }
                                 }
-                                if (!isSame) {
-                                    mMap.put(k, v);
-                                    changesMade = true;
-                                }
+                                mMap.put(k, v);
                             }
 
+                            mcr.changesMade = true;
                             if (hasListeners) {
-                                keysModified.add(k);
+                                mcr.keysModified.add(k);
                             }
                         }
 
                         mModified.clear();
                     }
-
-                    returnValue = writeFileLocked(changesMade);
                 }
+                return mcr;
+            }
 
-                if (hasListeners) {
-                    for (int i = keysModified.size() - 1; i >= 0; i--) {
-                        final String key = keysModified.get(i);
-                        for (OnSharedPreferenceChangeListener listener : listeners) {
+            public boolean commit() {
+                MemoryCommitResult mcr = commitToMemory();
+                SharedPreferencesImpl.this.enqueueDiskWrite(
+                    mcr, null /* sync write on this thread okay */);
+                try {
+                    mcr.writtenToDiskLatch.await();
+                } catch (InterruptedException e) {
+                    return false;
+                }
+                notifyListeners(mcr);
+                return mcr.writeToDiskResult;
+            }
+
+            private void notifyListeners(final MemoryCommitResult mcr) {
+                if (mcr.listeners == null || mcr.keysModified == null ||
+                    mcr.keysModified.size() == 0) {
+                    return;
+                }
+                if (Looper.myLooper() == Looper.getMainLooper()) {
+                    for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
+                        final String key = mcr.keysModified.get(i);
+                        for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
                             if (listener != null) {
                                 listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key);
                             }
                         }
                     }
+                } else {
+                    // Run this function on the main thread.
+                    ActivityThread.sMainThreadHandler.post(new Runnable() {
+                            public void run() {
+                                notifyListeners(mcr);
+                            }
+                        });
                 }
-
-                return returnValue;
             }
         }
 
-        public Editor edit() {
-            return new EditorImpl();
+        /**
+         * Enqueue an already-committed-to-memory result to be written
+         * to disk.
+         *
+         * They will be written to disk one-at-a-time in the order
+         * that they're enqueued.
+         *
+         * @param postWriteRunnable if non-null, we're being called
+         *   from startCommit() and this is the runnable to run after
+         *   the write proceeds.  if null (from a regular commit()),
+         *   then we're allowed to do this disk write on the main
+         *   thread (which in addition to reducing allocations and
+         *   creating a background thread, this has the advantage that
+         *   we catch them in userdebug StrictMode reports to convert
+         *   them where possible to startCommit...)
+         */
+        private void enqueueDiskWrite(final MemoryCommitResult mcr,
+                                      final Runnable postWriteRunnable) {
+            final Runnable writeToDiskRunnable = new Runnable() {
+                    public void run() {
+                        synchronized (mWritingToDiskLock) {
+                            writeToFile(mcr);
+                        }
+                        synchronized (SharedPreferencesImpl.this) {
+                            mDiskWritesInFlight--;
+                        }
+                        if (postWriteRunnable != null) {
+                            postWriteRunnable.run();
+                        }
+                    }
+                };
+
+            final boolean isFromSyncCommit = (postWriteRunnable == null);
+
+            // Typical #commit() path with fewer allocations, doing a write on
+            // the current thread.
+            if (isFromSyncCommit) {
+                boolean wasEmpty = false;
+                synchronized (SharedPreferencesImpl.this) {
+                    wasEmpty = mDiskWritesInFlight == 1;
+                }
+                if (wasEmpty) {
+                    writeToDiskRunnable.run();
+                    return;
+                }
+            }
+
+            QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
         }
 
-        private FileOutputStream createFileOutputStream(File file) {
+        private static FileOutputStream createFileOutputStream(File file) {
             FileOutputStream str = null;
             try {
                 str = new FileOutputStream(file);
@@ -2943,49 +3077,56 @@
             return str;
         }
 
-        private boolean writeFileLocked(boolean changesMade) {
+        // Note: must hold mWritingToDiskLock
+        private void writeToFile(MemoryCommitResult mcr) {
             // Rename the current file so it may be used as a backup during the next read
             if (mFile.exists()) {
-                if (!changesMade) {
+                if (!mcr.changesMade) {
                     // If the file already exists, but no changes were
                     // made to the underlying map, it's wasteful to
                     // re-write the file.  Return as if we wrote it
                     // out.
-                    return true;
+                    mcr.setDiskWriteResult(true);
+                    return;
                 }
                 if (!mBackupFile.exists()) {
                     if (!mFile.renameTo(mBackupFile)) {
                         Log.e(TAG, "Couldn't rename file " + mFile
                                 + " to backup file " + mBackupFile);
-                        return false;
+                        mcr.setDiskWriteResult(false);
+                        return;
                     }
                 } else {
                     mFile.delete();
                 }
             }
-            
+
             // Attempt to write the file, delete the backup and return true as atomically as
             // possible.  If any exception occurs, delete the new file; next time we will restore
             // from the backup.
             try {
                 FileOutputStream str = createFileOutputStream(mFile);
                 if (str == null) {
-                    return false;
+                    mcr.setDiskWriteResult(false);
+                    return;
                 }
-                XmlUtils.writeMapXml(mMap, str);
+                XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
                 str.close();
                 setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
-                if (FileUtils.getFileStatus(mFile.getPath(), mFileStatus)) {
-                    mTimestamp = mFileStatus.mtime;
+                FileStatus stat = new FileStatus();
+                if (FileUtils.getFileStatus(mFile.getPath(), stat)) {
+                    synchronized (this) {
+                        mTimestamp = stat.mtime;
+                    }
                 }
-                
                 // Writing was successful, delete the backup file if there is one.
                 mBackupFile.delete();
-                return true;
+                mcr.setDiskWriteResult(true);
+                return;
             } catch (XmlPullParserException e) {
-                Log.w(TAG, "writeFileLocked: Got exception:", e);
+                Log.w(TAG, "writeToFile: Got exception:", e);
             } catch (IOException e) {
-                Log.w(TAG, "writeFileLocked: Got exception:", e);
+                Log.w(TAG, "writeToFile: Got exception:", e);
             }
             // Clean up an unsuccessfully written file
             if (mFile.exists()) {
@@ -2993,7 +3134,7 @@
                     Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
                 }
             }
-            return false;
+            mcr.setDiskWriteResult(false);
         }
     }
 }
diff --git a/core/java/android/app/QueuedWork.java b/core/java/android/app/QueuedWork.java
new file mode 100644
index 0000000..af6bb1b
--- /dev/null
+++ b/core/java/android/app/QueuedWork.java
@@ -0,0 +1,91 @@
+/*
+ * 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.app;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Internal utility class to keep track of process-global work that's
+ * outstanding and hasn't been finished yet.
+ *
+ * This was created for writing SharedPreference edits out
+ * asynchronously so we'd have a mechanism to wait for the writes in
+ * Activity.onPause and similar places, but we may use this mechanism
+ * for other things in the future.
+ *
+ * @hide
+ */
+public class QueuedWork {
+
+    // The set of Runnables that will finish or wait on any async
+    // activities started by the application.
+    private static final ConcurrentLinkedQueue<Runnable> sPendingWorkFinishers =
+            new ConcurrentLinkedQueue<Runnable>();
+
+    private static ExecutorService sSingleThreadExecutor = null; // lazy, guarded by class
+
+    /**
+     * Returns a single-thread Executor shared by the entire process,
+     * creating it if necessary.
+     */
+    public static ExecutorService singleThreadExecutor() {
+        synchronized (QueuedWork.class) {
+            if (sSingleThreadExecutor == null) {
+                // TODO: can we give this single thread a thread name?
+                sSingleThreadExecutor = Executors.newSingleThreadExecutor();
+            }
+            return sSingleThreadExecutor;
+        }
+    }
+
+    /**
+     * Add a runnable to finish (or wait for) a deferred operation
+     * started in this context earlier.  Typically finished by e.g.
+     * an Activity#onPause.  Used by SharedPreferences$Editor#startCommit().
+     *
+     * Note that this doesn't actually start it running.  This is just
+     * a scratch set for callers doing async work to keep updated with
+     * what's in-flight.  In the common case, caller code
+     * (e.g. SharedPreferences) will pretty quickly call remove()
+     * after an add().  The only time these Runnables are run is from
+     * waitToFinish(), below.
+     */
+    public static void add(Runnable finisher) {
+        sPendingWorkFinishers.add(finisher);
+    }
+
+    public static void remove(Runnable finisher) {
+        sPendingWorkFinishers.remove(finisher);
+    }
+
+    /**
+     * Finishes or waits for async operations to complete.
+     * (e.g. SharedPreferences$Editor#startCommit writes)
+     *
+     * Is called from the Activity base class's onPause(), after
+     * BroadcastReceiver's onReceive, after Service command handling,
+     * etc.  (so async work is never lost)
+     */
+    public static void waitToFinish() {
+        Runnable toFinish;
+        while ((toFinish = sPendingWorkFinishers.poll()) != null) {
+            toFinish.run();
+        }
+    }
+}
diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java
index f1b1490..b3db2ac 100644
--- a/core/java/android/content/SharedPreferences.java
+++ b/core/java/android/content/SharedPreferences.java
@@ -40,7 +40,9 @@
         /**
          * Called when a shared preference is changed, added, or removed. This
          * may be called even if a preference is set to its existing value.
-         * 
+         *
+         * <p>This callback will be run on your main thread.
+         *
          * @param sharedPreferences The {@link SharedPreferences} that received
          *            the change.
          * @param key The key of the preference that was changed, added, or
@@ -187,9 +189,6 @@
          * <p>If you call this from an {@link android.app.Activity},
          * the base class will wait for any async commits to finish in
          * its {@link android.app.Activity#onPause}.</p>
-         *
-         * @return Returns true if the new values were successfully written
-         * to persistent storage.
          */
         void startCommit();
     }
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 2a32e54..3b2bf1e 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -563,13 +563,13 @@
             return mMessenger;
         }
     }
-    
+
     private final class MessengerImpl extends IMessenger.Stub {
         public void send(Message msg) {
             Handler.this.sendMessage(msg);
         }
     }
-    
+
     private final Message getPostMessage(Runnable r) {
         Message m = Message.obtain();
         m.callback = r;
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 197d976..1453329 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -1195,7 +1195,7 @@
 
     private void tryCommit(SharedPreferences.Editor editor) {
         if (mPreferenceManager.shouldCommit()) {
-            editor.commit();
+            editor.startCommit();
         }
     }
     
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index f00389b..abd66ae 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -1737,7 +1737,7 @@
                         mContext.getSharedPreferences(SHARED_PREFERENCES_NAME,
                                 mContext.MODE_PRIVATE).edit();
                     editor.putBoolean(SHARED_PREFERENCE_DOCK_ADDRESS + mDockAddress, true);
-                    editor.commit();
+                    editor.startCommit();
                 }
             }
         }
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 05c159b..052de97 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -5161,13 +5161,10 @@
                             }
                         } else {
                             if (mSelectingText) {
-                                // tapping on selection or controls does nothing
-                                if (!nativeHitSelection(contentX, contentY)) {
-                                    if (mMapTrackballToArrowKeys) { // gmail
-                                        copySelection();
-                                    }
-                                    selectionDone();
+                                if (nativeHitSelection(contentX, contentY)) {
+                                    copySelection();
                                 }
+                                selectionDone();
                                 break;
                             }
                             if (mTouchMode == TOUCH_INIT_MODE) {
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index c77416b..46f7db4 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -260,9 +260,15 @@
 
     /**
      * Sets a drawable as the content of this ImageView.
-     * 
+     *
+     * <p class="note">This does Bitmap reading and decoding on the UI
+     * thread, which can cause a latency hiccup.  If that's a concern,
+     * consider using {@link #setImageDrawable} or
+     * {@link #setImageBitmap} and
+     * {@link android.graphics.BitmapFactory} instead.</p>
+     *
      * @param resId the resource identifier of the the drawable
-     * 
+     *
      * @attr ref android.R.styleable#ImageView_src
      */
     @android.view.RemotableViewMethod
@@ -279,7 +285,13 @@
 
     /**
      * Sets the content of this ImageView to the specified Uri.
-     * 
+     *
+     * <p class="note">This does Bitmap reading and decoding on the UI
+     * thread, which can cause a latency hiccup.  If that's a concern,
+     * consider using {@link #setImageDrawable} or
+     * {@link #setImageBitmap} and
+     * {@link android.graphics.BitmapFactory} instead.</p>
+     *
      * @param uri The Uri of an image
      */
     @android.view.RemotableViewMethod
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 248f6eb..d1a14d2 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4303,6 +4303,15 @@
                 if (shouldAdvanceFocusOnEnter()) {
                     return 0;
                 }
+                break;
+
+                // Has to be done on key down (and not on key up) to correctly be intercepted.
+            case KeyEvent.KEYCODE_BACK:
+                if (mIsInTextSelectionMode) {
+                    stopTextSelectionMode();
+                    return -1;
+                }
+                break;
         }
 
         if (mInput != null) {
@@ -4456,6 +4465,7 @@
 
                     return super.onKeyUp(keyCode, event);
                 }
+                break;
         }
 
         if (mInput != null)
@@ -6618,9 +6628,10 @@
                 end = mPrevEnd;
             } else {
                 if ((mPrevStart != mPrevEnd) && (start == end)) {
-                    if ((start >= mPrevStart) && (start <= mPrevEnd)) {
+                    if ((start >= mPrevStart) && (start < mPrevEnd)) {
                         // Tapping inside the selection does nothing
                         Selection.setSelection((Spannable) mText, mPrevStart, mPrevEnd);
+                        showContextMenu();
                         return;
                     } else {
                         // Tapping outside stops selection mode, if any
@@ -7221,9 +7232,6 @@
                      setAlphabeticShortcut('v');
             }
 
-            menu.add(0, ID_STOP_SELECTING_TEXT, 0, com.android.internal.R.string.stopSelectingText).
-                 setOnMenuItemClickListener(handler);
-            
             added = true;
         } else {
             /*
@@ -7272,10 +7280,12 @@
                 }
             }
             
-            if (canPaste()) {
+            // Paste location is too imprecise. Only allow on empty text fields.
+            if (canPaste() && textIsOnlySpaces()) {
                 menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
                      setOnMenuItemClickListener(handler).
                      setAlphabeticShortcut('v');
+                added = true;
             }
 
             if (isInputMethodTarget()) {
@@ -7299,6 +7309,17 @@
         }
     }
 
+    private boolean textIsOnlySpaces() {
+        final int length = mTransformed.length();
+        for (int i=0; i<length; i++) {
+            final char c = mTransformed.charAt(i);
+            final int type = Character.getType(c);
+            if (type != Character.SPACE_SEPARATOR)
+                return false;
+        }
+        return true;
+    }
+
     /**
      * Returns whether this text view is a current input method target.  The
      * default implementation just checks with {@link InputMethodManager}.
@@ -7311,7 +7332,6 @@
     // Context menu entries
     private static final int ID_SELECT_ALL = android.R.id.selectAll;
     private static final int ID_START_SELECTING_TEXT = android.R.id.startSelectingText;
-    private static final int ID_STOP_SELECTING_TEXT = android.R.id.stopSelectingText;
     private static final int ID_CUT = android.R.id.cut;
     private static final int ID_COPY = android.R.id.copy;
     private static final int ID_PASTE = android.R.id.paste;
@@ -7358,10 +7378,6 @@
                 startTextSelectionMode();
                 return true;
 
-            case ID_STOP_SELECTING_TEXT:
-                stopTextSelectionMode();
-                return true;
-
             case ID_CUT:                
                 clip.setText(mTransformed.subSequence(min, max));
                 ((Editable) mText).delete(min, max);
@@ -7737,6 +7753,8 @@
         private boolean mStartIsDragged = false;
         // Starting time of the fade timer
         private long mFadeOutTimerStart;
+        // Used to detect a tap (vs drag) on the controller
+        private long mOnDownTimerStart;
         // The cursor controller images
         private final Handle mStartHandle, mEndHandle;
         // Offset between finger hot point on active cursor controller and actual cursor
@@ -7884,12 +7902,22 @@
                                     mOffsetX = (bounds.left + bounds.right) / 2.0f - x;
                                     mOffsetY = draggedHandle.mHotSpotVerticalPosition - y;
 
+                                    mOnDownTimerStart = event.getEventTime();
                                     ((ArrowKeyMovementMethod)mMovement).setCursorController(this);
                                 }
                             }
                         }
                         break;
 
+                    case MotionEvent.ACTION_UP:
+                        int time = (int) (event.getEventTime() - mOnDownTimerStart);
+
+                        if (time <= ViewConfiguration.getTapTimeout()) {
+                            // A tap on the controller (not a drag) opens the contextual Copy menu
+                            showContextMenu();
+                        }
+                        break;
+
                     case MotionEvent.ACTION_POINTER_DOWN:
                     case MotionEvent.ACTION_POINTER_UP:
                         // Handle multi-point gestures. Keep min and max offset positions.
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index b7d0c67..f3b9357 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -66,16 +66,6 @@
 
 // ----------------------------------------------------------------------------
 
-static void doThrow(JNIEnv* env, const char* exc, const char* msg = NULL)
-{
-    jclass npeClazz;
-
-    npeClazz = env->FindClass(exc);
-    LOG_FATAL_IF(npeClazz == NULL, "Unable to find class %s", exc);
-
-    env->ThrowNew(npeClazz, msg);
-}
-
 enum {
     STYLE_NUM_ENTRIES = 6,
     STYLE_TYPE = 0,
@@ -131,14 +121,14 @@
 
     LOGV("openAsset in %p (Java object %p)\n", am, clazz);
 
-    if (fileName == NULL || am == NULL) {
-        doThrow(env, "java/lang/NullPointerException");
+    if (fileName == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", "fileName");
         return -1;
     }
 
     if (mode != Asset::ACCESS_UNKNOWN && mode != Asset::ACCESS_RANDOM
         && mode != Asset::ACCESS_STREAMING && mode != Asset::ACCESS_BUFFER) {
-        doThrow(env, "java/lang/IllegalArgumentException");
+        jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode");
         return -1;
     }
 
@@ -146,7 +136,7 @@
     Asset* a = am->open(fileName8, (Asset::AccessMode)mode);
 
     if (a == NULL) {
-        doThrow(env, "java/io/FileNotFoundException", fileName8);
+        jniThrowException(env, "java/io/FileNotFoundException", fileName8);
         env->ReleaseStringUTFChars(fileName, fileName8);
         return -1;
     }
@@ -164,7 +154,7 @@
     delete a;
     
     if (fd < 0) {
-        doThrow(env, "java/io/FileNotFoundException",
+        jniThrowException(env, "java/io/FileNotFoundException",
                 "This file can not be opened as a file descriptor; it is probably compressed");
         return NULL;
     }
@@ -199,8 +189,8 @@
 
     LOGV("openAssetFd in %p (Java object %p)\n", am, clazz);
 
-    if (fileName == NULL || am == NULL) {
-        doThrow(env, "java/lang/NullPointerException");
+    if (fileName == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", "fileName");
         return NULL;
     }
 
@@ -208,7 +198,7 @@
     Asset* a = am->open(fileName8, Asset::ACCESS_RANDOM);
 
     if (a == NULL) {
-        doThrow(env, "java/io/FileNotFoundException", fileName8);
+        jniThrowException(env, "java/io/FileNotFoundException", fileName8);
         env->ReleaseStringUTFChars(fileName, fileName8);
         return NULL;
     }
@@ -231,14 +221,14 @@
 
     LOGV("openNonAssetNative in %p (Java object %p)\n", am, clazz);
 
-    if (fileName == NULL || am == NULL) {
-        doThrow(env, "java/lang/NullPointerException");
+    if (fileName == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", "fileName");
         return -1;
     }
 
     if (mode != Asset::ACCESS_UNKNOWN && mode != Asset::ACCESS_RANDOM
         && mode != Asset::ACCESS_STREAMING && mode != Asset::ACCESS_BUFFER) {
-        doThrow(env, "java/lang/IllegalArgumentException");
+        jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode");
         return -1;
     }
 
@@ -248,7 +238,7 @@
         : am->openNonAsset(fileName8, (Asset::AccessMode)mode);
 
     if (a == NULL) {
-        doThrow(env, "java/io/FileNotFoundException", fileName8);
+        jniThrowException(env, "java/io/FileNotFoundException", fileName8);
         env->ReleaseStringUTFChars(fileName, fileName8);
         return -1;
     }
@@ -271,8 +261,8 @@
 
     LOGV("openNonAssetFd in %p (Java object %p)\n", am, clazz);
 
-    if (fileName == NULL || am == NULL) {
-        doThrow(env, "java/lang/NullPointerException");
+    if (fileName == NULL ) {
+        jniThrowException(env, "java/lang/NullPointerException", "fileName");
         return NULL;
     }
 
@@ -282,7 +272,7 @@
         : am->openNonAsset(fileName8, Asset::ACCESS_RANDOM);
 
     if (a == NULL) {
-        doThrow(env, "java/io/FileNotFoundException", fileName8);
+        jniThrowException(env, "java/io/FileNotFoundException", fileName8);
         env->ReleaseStringUTFChars(fileName, fileName8);
         return NULL;
     }
@@ -301,8 +291,8 @@
         return NULL;
     }
 
-    if (fileName == NULL || am == NULL) {
-        doThrow(env, "java/lang/NullPointerException");
+    if (fileName == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", "fileName");
         return NULL;
     }
 
@@ -313,7 +303,7 @@
     env->ReleaseStringUTFChars(fileName, fileName8);
 
     if (dir == NULL) {
-        doThrow(env, "java/io/FileNotFoundException", fileName8);
+        jniThrowException(env, "java/io/FileNotFoundException", fileName8);
         return NULL;
     }
 
@@ -329,7 +319,6 @@
     jobjectArray array = env->NewObjectArray(dir->getFileCount(),
                                                 cls, NULL);
     if (array == NULL) {
-        doThrow(env, "java/lang/OutOfMemoryError");
         delete dir;
         return NULL;
     }
@@ -338,11 +327,11 @@
         const String8& name = dir->getFileName(i);
         jstring str = env->NewStringUTF(name.string());
         if (str == NULL) {
-            doThrow(env, "java/lang/OutOfMemoryError");
             delete dir;
             return NULL;
         }
         env->SetObjectArrayElement(array, i, str);
+        env->DeleteLocalRef(str);
     }
 
     delete dir;
@@ -358,7 +347,7 @@
     //printf("Destroying Asset Stream: %p\n", a);
 
     if (a == NULL) {
-        doThrow(env, "java/lang/NullPointerException");
+        jniThrowException(env, "java/lang/NullPointerException", "asset");
         return;
     }
 
@@ -371,7 +360,7 @@
     Asset* a = (Asset*)asset;
 
     if (a == NULL) {
-        doThrow(env, "java/lang/NullPointerException");
+        jniThrowException(env, "java/lang/NullPointerException", "asset");
         return -1;
     }
 
@@ -387,7 +376,7 @@
     Asset* a = (Asset*)asset;
 
     if (a == NULL || bArray == NULL) {
-        doThrow(env, "java/lang/NullPointerException");
+        jniThrowException(env, "java/lang/NullPointerException", "asset");
         return -1;
     }
 
@@ -397,7 +386,7 @@
     
     jsize bLen = env->GetArrayLength(bArray);
     if (off < 0 || off >= bLen || len < 0 || len > bLen || (off+len) > bLen) {
-        doThrow(env, "java/lang/IndexOutOfBoundsException");
+        jniThrowException(env, "java/lang/IndexOutOfBoundsException", "");
         return -1;
     }
 
@@ -408,7 +397,7 @@
     if (res > 0) return res;
 
     if (res < 0) {
-        doThrow(env, "java/io/IOException");
+        jniThrowException(env, "java/io/IOException", "");
     }
     return -1;
 }
@@ -420,7 +409,7 @@
     Asset* a = (Asset*)asset;
 
     if (a == NULL) {
-        doThrow(env, "java/lang/NullPointerException");
+        jniThrowException(env, "java/lang/NullPointerException", "asset");
         return -1;
     }
 
@@ -434,7 +423,7 @@
     Asset* a = (Asset*)asset;
 
     if (a == NULL) {
-        doThrow(env, "java/lang/NullPointerException");
+        jniThrowException(env, "java/lang/NullPointerException", "asset");
         return -1;
     }
 
@@ -447,7 +436,7 @@
     Asset* a = (Asset*)asset;
 
     if (a == NULL) {
-        doThrow(env, "java/lang/NullPointerException");
+        jniThrowException(env, "java/lang/NullPointerException", "asset");
         return -1;
     }
 
@@ -458,7 +447,7 @@
                                                        jstring path)
 {
     if (path == NULL) {
-        doThrow(env, "java/lang/NullPointerException");
+        jniThrowException(env, "java/lang/NullPointerException", "path");
         return JNI_FALSE;
     }
 
@@ -490,7 +479,7 @@
                                                 jstring locale)
 {
     if (locale == NULL) {
-        doThrow(env, "java/lang/NullPointerException");
+        jniThrowException(env, "java/lang/NullPointerException", "locale");
         return;
     }
 
@@ -525,7 +514,12 @@
     }
 
     for (int i=0; i<N; i++) {
-        env->SetObjectArrayElement(result, i, env->NewStringUTF(locales[i].string()));
+        jstring str = env->NewStringUTF(locales[i].string());
+        if (str == NULL) {
+            return NULL;
+        }
+        env->SetObjectArrayElement(result, i, str);
+        env->DeleteLocalRef(str);
     }
 
     return result;
@@ -576,7 +570,7 @@
                                                             jstring defPackage)
 {
     if (name == NULL) {
-        doThrow(env, "java/lang/NullPointerException");
+        jniThrowException(env, "java/lang/NullPointerException", "name");
         return 0;
     }
 
@@ -814,14 +808,10 @@
     }
     String8 name(am->getAssetPath((void*)cookie));
     if (name.length() == 0) {
-        doThrow(env, "java/lang/IndexOutOfBoundsException");
+        jniThrowException(env, "java/lang/IndexOutOfBoundsException", "Empty cookie name");
         return NULL;
     }
     jstring str = env->NewStringUTF(name.string());
-    if (str == NULL) {
-        doThrow(env, "java/lang/OutOfMemoryError");
-        return NULL;
-    }
     return str;
 }
 
@@ -889,7 +879,7 @@
     const ResTable& res(theme->getResTable());
     
     if (tag == NULL) {
-        doThrow(env, "java/lang/NullPointerException");
+        jniThrowException(env, "java/lang/NullPointerException", "tag");
         return;
     }
     
@@ -917,8 +907,16 @@
                                                         jintArray outValues,
                                                         jintArray outIndices)
 {
-    if (themeToken == 0 || attrs == NULL || outValues == NULL) {
-        doThrow(env, "java/lang/NullPointerException");
+    if (themeToken == 0) {
+        jniThrowException(env, "java/lang/NullPointerException", "theme token");
+        return JNI_FALSE;
+    }
+    if (attrs == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", "attrs");
+        return JNI_FALSE;
+    }
+    if (outValues == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", "out values");
         return JNI_FALSE;
     }
 
@@ -934,13 +932,13 @@
     const jsize NI = env->GetArrayLength(attrs);
     const jsize NV = env->GetArrayLength(outValues);
     if (NV < (NI*STYLE_NUM_ENTRIES)) {
-        doThrow(env, "java/lang/IndexOutOfBoundsException");
+        jniThrowException(env, "java/lang/IndexOutOfBoundsException", "out values too small");
         return JNI_FALSE;
     }
 
     jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0);
     if (src == NULL) {
-        doThrow(env, "java/lang/OutOfMemoryError");
+        jniThrowException(env, "java/lang/OutOfMemoryError", "");
         return JNI_FALSE;
     }
 
@@ -948,7 +946,7 @@
     jint* dest = baseDest;
     if (dest == NULL) {
         env->ReleasePrimitiveArrayCritical(attrs, src, 0);
-        doThrow(env, "java/lang/OutOfMemoryError");
+        jniThrowException(env, "java/lang/OutOfMemoryError", "");
         return JNI_FALSE;
     }
 
@@ -1152,8 +1150,16 @@
                                                         jintArray outValues,
                                                         jintArray outIndices)
 {
-    if (xmlParserToken == 0 || attrs == NULL || outValues == NULL) {
-        doThrow(env, "java/lang/NullPointerException");
+    if (xmlParserToken == 0) {
+        jniThrowException(env, "java/lang/NullPointerException", "xmlParserToken");
+        return JNI_FALSE;
+    }
+    if (attrs == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", "attrs");
+        return JNI_FALSE;
+    }
+    if (outValues == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", "out values");
         return JNI_FALSE;
     }
     
@@ -1169,13 +1175,13 @@
     const jsize NI = env->GetArrayLength(attrs);
     const jsize NV = env->GetArrayLength(outValues);
     if (NV < (NI*STYLE_NUM_ENTRIES)) {
-        doThrow(env, "java/lang/IndexOutOfBoundsException");
+        jniThrowException(env, "java/lang/IndexOutOfBoundsException", "out values too small");
         return JNI_FALSE;
     }
     
     jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0);
     if (src == NULL) {
-        doThrow(env, "java/lang/OutOfMemoryError");
+        jniThrowException(env, "java/lang/OutOfMemoryError", "");
         return JNI_FALSE;
     }
     
@@ -1183,7 +1189,7 @@
     jint* dest = baseDest;
     if (dest == NULL) {
         env->ReleasePrimitiveArrayCritical(attrs, src, 0);
-        doThrow(env, "java/lang/OutOfMemoryError");
+        jniThrowException(env, "java/lang/OutOfMemoryError", "");
         return JNI_FALSE;
     }
     
@@ -1306,7 +1312,7 @@
                                                         jintArray outValues)
 {
     if (outValues == NULL) {
-        doThrow(env, "java/lang/NullPointerException");
+        jniThrowException(env, "java/lang/NullPointerException", "out values");
         return JNI_FALSE;
     }
     
@@ -1324,7 +1330,7 @@
     jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
     jint* dest = baseDest;
     if (dest == NULL) {
-        doThrow(env, "java/lang/OutOfMemoryError");
+        jniThrowException(env, "java/lang/OutOfMemoryError", "");
         return JNI_FALSE;
     }
     
@@ -1399,8 +1405,8 @@
 
     LOGV("openXmlAsset in %p (Java object %p)\n", am, clazz);
 
-    if (fileName == NULL || am == NULL) {
-        doThrow(env, "java/lang/NullPointerException");
+    if (fileName == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", "fileName");
         return 0;
     }
 
@@ -1410,7 +1416,7 @@
         : am->openNonAsset(fileName8, Asset::ACCESS_BUFFER);
 
     if (a == NULL) {
-        doThrow(env, "java/io/FileNotFoundException", fileName8);
+        jniThrowException(env, "java/io/FileNotFoundException", fileName8);
         env->ReleaseStringUTFChars(fileName, fileName8);
         return 0;
     }
@@ -1422,7 +1428,7 @@
     delete a;
 
     if (err != NO_ERROR) {
-        doThrow(env, "java/io/FileNotFoundException", "Corrupt XML binary file");
+        jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file");
         return 0;
     }
 
@@ -1446,7 +1452,7 @@
 
     jintArray array = env->NewIntArray(N * 2);
     if (array == NULL) {
-        doThrow(env, "java/lang/OutOfMemoryError");
+        jniThrowException(env, "java/lang/OutOfMemoryError", "");
         res.unlockBag(startOfBag);
         return NULL;
     }
@@ -1540,13 +1546,12 @@
                 res.unlockBag(startOfBag);
                 return NULL;
             }
-        }
 
-        env->SetObjectArrayElement(array, i, str);
+            env->SetObjectArrayElement(array, i, str);
 
-        // If we have a large amount of strings in our array, we might
-        // overflow the local reference table of the VM.
-        if (str != NULL) {
+            // str is not NULL at that point, otherwise ExceptionCheck would have been true.
+            // If we have a large amount of strings in our array, we might
+            // overflow the local reference table of the VM.
             env->DeleteLocalRef(str);
         }
     }
@@ -1571,7 +1576,7 @@
 
     jintArray array = env->NewIntArray(N);
     if (array == NULL) {
-        doThrow(env, "java/lang/OutOfMemoryError");
+        jniThrowException(env, "java/lang/OutOfMemoryError", "");
         res.unlockBag(startOfBag);
         return NULL;
     }
@@ -1603,7 +1608,7 @@
 {
     AssetManager* am = new AssetManager();
     if (am == NULL) {
-        doThrow(env, "java/lang/OutOfMemoryError");
+        jniThrowException(env, "java/lang/OutOfMemoryError", "");
         return;
     }
 
@@ -1637,11 +1642,6 @@
     }
     
     jstring str = env->NewStringUTF(alloc.string());
-    if (str == NULL) {
-        doThrow(env, "java/lang/OutOfMemoryError");
-        return NULL;
-    }
-    
     return str;
 }
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 52abe45..03b721c 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1853,12 +1853,9 @@
     <!-- Item on EditText context menu. This action is used to select all text in the edit field. -->
     <string name="selectAll">Select all</string>
 
-    <!-- Item on EditText context menu. This action is used to start selecting text in the edit field. -->
+    <!-- Item on EditText context menu. This action is used to start selecting text in the edit field. [CHAR LIMIT=20] -->
     <string name="selectText">Select word</string>
 
-    <!-- Item on EditText context menu. This action is used to stop selecting text in the edit field. -->
-    <string name="stopSelectingText">Stop selecting text</string>
-
     <!-- Item on EditText context menu.  This action is used to cut selected the text into the clipboard.  -->
     <string name="cut">Cut</string>
 
diff --git a/docs/html/guide/topics/providers/content-providers.jd b/docs/html/guide/topics/providers/content-providers.jd
index 30f8d8c..da4e7a1 100644
--- a/docs/html/guide/topics/providers/content-providers.jd
+++ b/docs/html/guide/topics/providers/content-providers.jd
@@ -779,7 +779,7 @@
 requested.  Here is the general format for each type:</p></li>
 
 <ul>
-<li><p>For a single record:&nbsp;&nbsp;&nbsp; {@code vnd.android.cursor.item/vnd.<em>yourcompanyname.contenttype</em}</p> 
+<li><p>For a single record:&nbsp;&nbsp;&nbsp; {@code vnd.android.cursor.item/vnd.<em>yourcompanyname.contenttype</em>}</p>
 
 <p>For example, a request for train record 122, like this URI,</p>
 <p style="margin-left: 2em">{@code content://com.example.transportationprovider/trains/122}</p>
diff --git a/include/private/surfaceflinger/SharedBufferStack.h b/include/private/surfaceflinger/SharedBufferStack.h
index d016dfa..d689667 100644
--- a/include/private/surfaceflinger/SharedBufferStack.h
+++ b/include/private/surfaceflinger/SharedBufferStack.h
@@ -151,7 +151,6 @@
     ~SharedBufferBase();
     status_t getStatus() const;
     int32_t getIdentity() const;
-    size_t getFrontBuffer() const;
     String8 dump(char const* prefix) const;
 
 protected:
@@ -226,6 +225,11 @@
         inline ssize_t operator()();
     };
 
+    struct DequeueUpdate : public UpdateBase {
+        inline DequeueUpdate(SharedBufferBase* sbb);
+        inline ssize_t operator()();
+    };
+
     struct UndoDequeueUpdate : public UpdateBase {
         inline UndoDequeueUpdate(SharedBufferBase* sbb);
         inline ssize_t operator()();
diff --git a/libs/surfaceflinger_client/SharedBufferStack.cpp b/libs/surfaceflinger_client/SharedBufferStack.cpp
index 4ad9f86..38b2fae 100644
--- a/libs/surfaceflinger_client/SharedBufferStack.cpp
+++ b/libs/surfaceflinger_client/SharedBufferStack.cpp
@@ -191,12 +191,6 @@
     return stack.identity;
 }
 
-size_t SharedBufferBase::getFrontBuffer() const
-{
-    SharedBufferStack& stack( *mSharedStack );
-    return size_t( stack.head );
-}
-
 String8 SharedBufferBase::dump(char const* prefix) const
 {
     const size_t SIZE = 1024;
@@ -281,6 +275,16 @@
     return NO_ERROR;
 }
 
+SharedBufferClient::DequeueUpdate::DequeueUpdate(SharedBufferBase* sbb)
+    : UpdateBase(sbb) {
+}
+ssize_t SharedBufferClient::DequeueUpdate::operator()() {
+    if (android_atomic_dec(&stack.available) == 0) {
+        LOGW("dequeue probably called from multiple threads!");
+    }
+    return NO_ERROR;
+}
+
 SharedBufferClient::UndoDequeueUpdate::UndoDequeueUpdate(SharedBufferBase* sbb)
     : UpdateBase(sbb) {    
 }
@@ -388,12 +392,8 @@
     if (err != NO_ERROR)
         return ssize_t(err);
 
-    // NOTE: 'stack.available' is part of the conditions, however
-    // decrementing it, never changes any conditions, so we don't need
-    // to do this as part of an update.
-    if (android_atomic_dec(&stack.available) == 0) {
-        LOGW("dequeue probably called from multiple threads!");
-    }
+    DequeueUpdate update(this);
+    updateCondition( update );
 
     undoDequeueTail = tail;
     int dequeued = stack.index[tail];
diff --git a/libs/utils/ZipFileRO.cpp b/libs/utils/ZipFileRO.cpp
index 604f558..a0e01c6 100644
--- a/libs/utils/ZipFileRO.cpp
+++ b/libs/utils/ZipFileRO.cpp
@@ -508,8 +508,8 @@
         }
 
         if (get4LE(lfhBuf) != kLFHSignature) {
-            LOGW("didn't find signature at start of lfh, offset=%ld\n",
-                localHdrOffset);
+            LOGW("didn't find signature at start of lfh, offset=%ld (got 0x%08lx, expected 0x%08x)\n",
+                localHdrOffset, get4LE(lfhBuf), kLFHSignature);
             return false;
         }
 
diff --git a/services/java/com/android/server/BootReceiver.java b/services/java/com/android/server/BootReceiver.java
index f409751..d15a058 100644
--- a/services/java/com/android/server/BootReceiver.java
+++ b/services/java/com/android/server/BootReceiver.java
@@ -165,7 +165,9 @@
         if (prefs != null) {
             long lastTime = prefs.getLong(filename, 0);
             if (lastTime == fileTime) return;  // Already logged this particular file
-            prefs.edit().putLong(filename, fileTime).commit();
+            // TODO: move all these SharedPreferences Editor commits
+            // outside this function to the end of logBootEvents
+            prefs.edit().putLong(filename, fileTime).startCommit();
         }
 
         Slog.i(TAG, "Copying " + filename + " to DropBox (" + tag + ")");