Merge change I2e90d640 into eclair

* changes:
  Remove the activity restart between urls.
diff --git a/api/current.xml b/api/current.xml
index 8f83e39..47dc08a 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -99410,6 +99410,17 @@
  visibility="public"
 >
 </field>
+<field name="ECLAIR_MR1"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="6"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 </class>
 <class name="Bundle"
  extends="java.lang.Object"
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index e59a987..0f7ef22 100755
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -191,6 +191,7 @@
     private int mLastCodeX;
     private int mLastCodeY;
     private int mCurrentKey = NOT_A_KEY;
+    private int mDownKey = NOT_A_KEY;
     private long mLastKeyTime;
     private long mCurrentKeyTime;
     private int[] mKeyIndices = new int[12];
@@ -202,6 +203,10 @@
     private boolean mAbortKey;
     private Key mInvalidatedKey;
     private Rect mClipRegion = new Rect(0, 0, 0, 0);
+    private boolean mPossiblePoly;
+    private SwipeTracker mSwipeTracker = new SwipeTracker();
+    private int mSwipeThreshold;
+    private boolean mDisambiguateSwipe;
 
     // Variables for dealing with multiple pointers
     private int mOldPointerCount = 1;
@@ -351,7 +356,10 @@
         mPadding = new Rect(0, 0, 0, 0);
         mMiniKeyboardCache = new HashMap<Key,View>();
         mKeyBackground.getPadding(mPadding);
-        
+
+        mSwipeThreshold = (int) (500 * getResources().getDisplayMetrics().density);
+        mDisambiguateSwipe = getResources().getBoolean(
+                com.android.internal.R.bool.config_swipeDisambiguation);
         resetMultiTap();
         initGestureDetector();
     }
@@ -361,22 +369,49 @@
             @Override
             public boolean onFling(MotionEvent me1, MotionEvent me2, 
                     float velocityX, float velocityY) {
+                if (mPossiblePoly) return false;
                 final float absX = Math.abs(velocityX);
                 final float absY = Math.abs(velocityY);
-                if (velocityX > 500 && absY < absX) {
-                    swipeRight();
-                    return true;
-                } else if (velocityX < -500 && absY < absX) {
-                    swipeLeft();
-                    return true;
-                } else if (velocityY < -500 && absX < absY) {
-                    swipeUp();
-                    return true;
-                } else if (velocityY > 500 && absX < 200) {
-                    swipeDown();
-                    return true;
-                } else if (absX > 800 || absY > 800) {
-                    return true;
+                float deltaX = me2.getX() - me1.getX();
+                float deltaY = me2.getY() - me1.getY();
+                int travelX = getWidth() / 2; // Half the keyboard width
+                int travelY = getHeight() / 2; // Half the keyboard height
+                mSwipeTracker.computeCurrentVelocity(1000);
+                final float endingVelocityX = mSwipeTracker.getXVelocity();
+                final float endingVelocityY = mSwipeTracker.getYVelocity();
+                boolean sendDownKey = false;
+                if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) {
+                    if (mDisambiguateSwipe && endingVelocityX < velocityX / 4) {
+                        sendDownKey = true;
+                    } else {
+                        swipeRight();
+                        return true;
+                    }
+                } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) {
+                    if (mDisambiguateSwipe && endingVelocityX > velocityX / 4) {
+                        sendDownKey = true;
+                    } else {
+                        swipeLeft();
+                        return true;
+                    }
+                } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) {
+                    if (mDisambiguateSwipe && endingVelocityY > velocityY / 4) {
+                        sendDownKey = true;
+                    } else {
+                        swipeUp();
+                        return true;
+                    }
+                } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) {
+                    if (mDisambiguateSwipe && endingVelocityY < velocityY / 4) {
+                        sendDownKey = true;
+                    } else {
+                        swipeDown();
+                        return true;
+                    }
+                }
+
+                if (sendDownKey) {
+                    detectAndSendKey(mDownKey, mStartX, mStartY, me1.getEventTime());
                 }
                 return false;
             }
@@ -743,8 +778,7 @@
         return primaryIndex;
     }
 
-    private void detectAndSendKey(int x, int y, long eventTime) {
-        int index = mCurrentKey;
+    private void detectAndSendKey(int index, int x, int y, long eventTime) {
         if (index != NOT_A_KEY && index < mKeys.length) {
             final Key key = mKeys[index];
             if (key.text != null) {
@@ -1026,51 +1060,64 @@
         return false;
     }
 
+    private long mOldEventTime;
+    private boolean mUsedVelocity;
+
     @Override
     public boolean onTouchEvent(MotionEvent me) {
         // Convert multi-pointer up/down events to single up/down events to 
         // deal with the typical multi-pointer behavior of two-thumb typing
-        int pointerCount = me.getPointerCount();
+        final int pointerCount = me.getPointerCount();
+        final int action = me.getAction();
         boolean result = false;
+        final long now = me.getEventTime();
+
         if (pointerCount != mOldPointerCount) {
-            long now = me.getEventTime();
             if (pointerCount == 1) {
                 // Send a down event for the latest pointer
                 MotionEvent down = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN,
                         me.getX(), me.getY(), me.getMetaState());
-                result = onModifiedTouchEvent(down);
+                result = onModifiedTouchEvent(down, false);
                 down.recycle();
                 // If it's an up action, then deliver the up as well.
-                if (me.getAction() == MotionEvent.ACTION_UP) {
-                    result = onModifiedTouchEvent(me);
+                if (action == MotionEvent.ACTION_UP) {
+                    result = onModifiedTouchEvent(me, true);
                 }
             } else {
                 // Send an up event for the last pointer
                 MotionEvent up = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP,
                         mOldPointerX, mOldPointerY, me.getMetaState());
-                result = onModifiedTouchEvent(up);
+                result = onModifiedTouchEvent(up, true);
                 up.recycle();
             }
         } else {
             if (pointerCount == 1) {
+                result = onModifiedTouchEvent(me, false);
                 mOldPointerX = me.getX();
                 mOldPointerY = me.getY();
-                result = onModifiedTouchEvent(me);
             } else {
                 // Don't do anything when 2 pointers are down and moving.
                 result = true;
             }
         }
         mOldPointerCount = pointerCount;
+
         return result;
     }
 
-    private boolean onModifiedTouchEvent(MotionEvent me) {
+    private boolean onModifiedTouchEvent(MotionEvent me, boolean possiblePoly) {
         int touchX = (int) me.getX() - mPaddingLeft;
         int touchY = (int) me.getY() + mVerticalCorrection - mPaddingTop;
-        int action = me.getAction();
-        long eventTime = me.getEventTime();
+        final int action = me.getAction();
+        final long eventTime = me.getEventTime();
+        mOldEventTime = eventTime;
         int keyIndex = getKeyIndices(touchX, touchY, null);
+        mPossiblePoly = possiblePoly;
+
+        // Track the last few movements to look for spurious swipes.
+        if (action == MotionEvent.ACTION_DOWN) mSwipeTracker.clear();
+        mSwipeTracker.addMovement(me);
+
         if (mGestureDetector.onTouchEvent(me)) {
             showPreview(NOT_A_KEY);
             mHandler.removeMessages(MSG_REPEAT);
@@ -1095,6 +1142,7 @@
                 mCurrentKeyTime = 0;
                 mLastKey = NOT_A_KEY;
                 mCurrentKey = keyIndex;
+                mDownKey = keyIndex;
                 mDownTime = me.getEventTime();
                 mLastMoveTime = mDownTime;
                 checkMultiTap(eventTime, keyIndex);
@@ -1167,11 +1215,17 @@
                 Arrays.fill(mKeyIndices, NOT_A_KEY);
                 // If we're not on a repeating key (which sends on a DOWN event)
                 if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) {
-                    detectAndSendKey(touchX, touchY, eventTime);
+                    detectAndSendKey(mCurrentKey, touchX, touchY, eventTime);
                 }
                 invalidateKey(keyIndex);
                 mRepeatKeyIndex = NOT_A_KEY;
                 break;
+            case MotionEvent.ACTION_CANCEL:
+                removeMessages();
+                mAbortKey = true;
+                showPreview(NOT_A_KEY);
+                invalidateKey(mCurrentKey);
+                break;
         }
         mLastX = touchX;
         mLastY = touchY;
@@ -1180,7 +1234,7 @@
 
     private boolean repeatKey() {
         Key key = mKeys[mRepeatKeyIndex];
-        detectAndSendKey(key.x, key.y, mLastTapTime);
+        detectAndSendKey(mCurrentKey, key.x, key.y, mLastTapTime);
         return true;
     }
     
@@ -1265,4 +1319,114 @@
             resetMultiTap();
         }
     }
+
+    private static class SwipeTracker {
+
+        static final int NUM_PAST = 4;
+        static final int LONGEST_PAST_TIME = 200;
+
+        final float mPastX[] = new float[NUM_PAST];
+        final float mPastY[] = new float[NUM_PAST];
+        final long mPastTime[] = new long[NUM_PAST];
+
+        float mYVelocity;
+        float mXVelocity;
+
+        public void clear() {
+            mPastTime[0] = 0;
+        }
+
+        public void addMovement(MotionEvent ev) {
+            long time = ev.getEventTime();
+            final int N = ev.getHistorySize();
+            for (int i=0; i<N; i++) {
+                addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i),
+                        ev.getHistoricalEventTime(i));
+            }
+            addPoint(ev.getX(), ev.getY(), time);
+        }
+
+        private void addPoint(float x, float y, long time) {
+            int drop = -1;
+            int i;
+            final long[] pastTime = mPastTime;
+            for (i=0; i<NUM_PAST; i++) {
+                if (pastTime[i] == 0) {
+                    break;
+                } else if (pastTime[i] < time-LONGEST_PAST_TIME) {
+                    drop = i;
+                }
+            }
+            if (i == NUM_PAST && drop < 0) {
+                drop = 0;
+            }
+            if (drop == i) drop--;
+            final float[] pastX = mPastX;
+            final float[] pastY = mPastY;
+            if (drop >= 0) {
+                final int start = drop+1;
+                final int count = NUM_PAST-drop-1;
+                System.arraycopy(pastX, start, pastX, 0, count);
+                System.arraycopy(pastY, start, pastY, 0, count);
+                System.arraycopy(pastTime, start, pastTime, 0, count);
+                i -= (drop+1);
+            }
+            pastX[i] = x;
+            pastY[i] = y;
+            pastTime[i] = time;
+            i++;
+            if (i < NUM_PAST) {
+                pastTime[i] = 0;
+            }
+        }
+
+        public void computeCurrentVelocity(int units) {
+            computeCurrentVelocity(units, Float.MAX_VALUE);
+        }
+
+        public void computeCurrentVelocity(int units, float maxVelocity) {
+            final float[] pastX = mPastX;
+            final float[] pastY = mPastY;
+            final long[] pastTime = mPastTime;
+
+            final float oldestX = pastX[0];
+            final float oldestY = pastY[0];
+            final long oldestTime = pastTime[0];
+            float accumX = 0;
+            float accumY = 0;
+            int N=0;
+            while (N < NUM_PAST) {
+                if (pastTime[N] == 0) {
+                    break;
+                }
+                N++;
+            }
+
+            for (int i=1; i < N; i++) {
+                final int dur = (int)(pastTime[i] - oldestTime);
+                if (dur == 0) continue;
+                float dist = pastX[i] - oldestX;
+                float vel = (dist/dur) * units;   // pixels/frame.
+                if (accumX == 0) accumX = vel;
+                else accumX = (accumX + vel) * .5f;
+
+                dist = pastY[i] - oldestY;
+                vel = (dist/dur) * units;   // pixels/frame.
+                if (accumY == 0) accumY = vel;
+                else accumY = (accumY + vel) * .5f;
+            }
+            mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity)
+                    : Math.min(accumX, maxVelocity);
+            mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity)
+                    : Math.min(accumY, maxVelocity);
+        }
+
+        public float getXVelocity() {
+            return mXVelocity;
+        }
+
+        public float getYVelocity() {
+            return mYVelocity;
+        }
+    }
 }
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 0f8bc08..d4aaba3 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -103,16 +103,19 @@
          * October 2008: The original, first, version of Android.  Yay!
          */
         public static final int BASE = 1;
+        
         /**
          * February 2009: First Android update, officially called 1.1.
          */
         public static final int BASE_1_1 = 2;
+        
         /**
          * May 2009: Android 1.5.
          */
         public static final int CUPCAKE = 3;
+        
         /**
-         * Current work on "Donut" development branch.
+         * September 2009: Android 1.6.
          * 
          * <p>Applications targeting this or a later release will get these
          * new changes in behavior:</p>
@@ -133,8 +136,9 @@
          * </ul>
          */
         public static final int DONUT = 4;
+        
         /**
-         * Current work on "Eclair" development branch.
+         * November 2009: Android 2.0
          * 
          * <p>Applications targeting this or a later release will get these
          * new changes in behavior:</p>
@@ -152,6 +156,11 @@
          * </ul>
          */
         public static final int ECLAIR = 5;
+        
+        /**
+         * Current work on Eclair MR1.
+         */
+        public static final int ECLAIR_MR1 = 6;
     }
     
     /** The type of build, like "user" or "eng". */
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 86685fb..a5a4852 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -1643,8 +1643,6 @@
 
     final DrawFilter mZoomFilter =
                     new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG);
-    final DrawFilter mScrollFilter =
-                    new PaintFlagsDrawFilter(SCROLL_BITS, 0);
 
     /* package */ void drawContentPicture(Canvas canvas, int color,
                                           boolean animatingZoom,
@@ -1653,7 +1651,7 @@
         if (animatingZoom) {
             df = mZoomFilter;
         } else if (animatingScroll) {
-            df = mScrollFilter;
+            df = null;
         }
         canvas.setDrawFilter(df);
         boolean tookTooLong = nativeDrawContent(canvas, color);
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 057e10a..bd6e7b4 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -204,4 +204,7 @@
          This must be overridden in platform specific overlays -->
     <integer-array name="config_autoBrightnessKeyboardBacklightValues">
     </integer-array>
+
+    <!-- Enables swipe versus poly-finger touch disambiguation in the KeyboardView -->
+    <bool name="config_swipeDisambiguation">true</bool>
 </resources>
diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp
index cc39dac..eb388af 100644
--- a/libs/rs/rsContext.cpp
+++ b/libs/rs/rsContext.cpp
@@ -30,8 +30,21 @@
 
 pthread_key_t Context::gThreadTLSKey = 0;
 uint32_t Context::gThreadTLSKeyCount = 0;
+uint32_t Context::gGLContextCount = 0;
 pthread_mutex_t Context::gInitMutex = PTHREAD_MUTEX_INITIALIZER;
 
+static void checkEglError(const char* op, EGLBoolean returnVal = EGL_TRUE) {
+    if (returnVal != EGL_TRUE) {
+        fprintf(stderr, "%s() returned %d\n", op, returnVal);
+    }
+
+    for (EGLint error = eglGetError(); error != EGL_SUCCESS; error
+            = eglGetError()) {
+        fprintf(stderr, "after %s() eglError %s (0x%x)\n", op, EGLUtils::strerror(error),
+                error);
+    }
+}
+
 void Context::initEGL()
 {
     mEGL.mNumConfigs = -1;
@@ -61,7 +74,10 @@
 
     LOGV("initEGL start");
     mEGL.mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+    checkEglError("eglGetDisplay");
+
     eglInitialize(mEGL.mDisplay, &mEGL.mMajorVersion, &mEGL.mMinorVersion);
+    checkEglError("eglInitialize");
 
     status_t err = EGLUtils::selectConfigForNativeWindow(mEGL.mDisplay, configAttribs, mWndSurface, &mEGL.mConfig);
     if (err) {
@@ -76,9 +92,24 @@
              android_createDisplaySurface(),
              NULL);
     }
+    checkEglError("eglCreateWindowSurface");
+    if (mEGL.mSurface == EGL_NO_SURFACE) {
+        LOGE("eglCreateWindowSurface returned EGL_NO_SURFACE");
+    }
 
-    mEGL.mContext = eglCreateContext(mEGL.mDisplay, mEGL.mConfig, NULL, NULL);
-    eglMakeCurrent(mEGL.mDisplay, mEGL.mSurface, mEGL.mSurface, mEGL.mContext);
+    mEGL.mContext = eglCreateContext(mEGL.mDisplay, mEGL.mConfig, EGL_NO_CONTEXT, NULL);
+    checkEglError("eglCreateContext");
+    if (mEGL.mContext == EGL_NO_CONTEXT) {
+        LOGE("eglCreateContext returned EGL_NO_CONTEXT");
+    }
+    gGLContextCount++;
+
+    EGLBoolean ret = eglMakeCurrent(mEGL.mDisplay, mEGL.mSurface, mEGL.mSurface, mEGL.mContext);
+    checkEglError("eglCreateContext", ret);
+    if (mEGL.mContext == EGL_NO_CONTEXT) {
+        LOGE("eglCreateContext returned EGL_NO_CONTEXT");
+    }
+
     eglQuerySurface(mEGL.mDisplay, mEGL.mSurface, EGL_WIDTH, &mEGL.mWidth);
     eglQuerySurface(mEGL.mDisplay, mEGL.mSurface, EGL_HEIGHT, &mEGL.mHeight);
 
@@ -101,6 +132,24 @@
     }
 }
 
+void Context::deinitEGL()
+{
+    EGLBoolean ret = eglMakeCurrent(mEGL.mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+    checkEglError("eglCreateContext", ret);
+    if (mEGL.mContext == EGL_NO_CONTEXT) {
+        LOGE("eglCreateContext returned EGL_NO_CONTEXT");
+    }
+
+    eglDestroyContext(mEGL.mDisplay, mEGL.mContext);
+    checkEglError("eglDestroyContext");
+
+    gGLContextCount--;
+    if (!gGLContextCount) {
+        eglTerminate(mEGL.mDisplay);
+    }
+}
+
+
 bool Context::runScript(Script *s, uint32_t launchID)
 {
     ObjectBaseRef<ProgramFragment> frag(mFragment);
@@ -232,7 +281,9 @@
      rsc->props.mLogScripts = getProp("debug.rs.script");
      rsc->props.mLogObjects = getProp("debug.rs.objects");
 
+     pthread_mutex_lock(&gInitMutex);
      rsc->initEGL();
+     pthread_mutex_unlock(&gInitMutex);
 
      ScriptTLSStruct *tlsStruct = new ScriptTLSStruct;
      if (!tlsStruct) {
@@ -294,7 +345,11 @@
      glClearColor(0,0,0,0);
      glClear(GL_COLOR_BUFFER_BIT);
      eglSwapBuffers(rsc->mEGL.mDisplay, rsc->mEGL.mSurface);
-     eglTerminate(rsc->mEGL.mDisplay);
+
+     pthread_mutex_lock(&gInitMutex);
+     rsc->deinitEGL();
+     pthread_mutex_unlock(&gInitMutex);
+
      rsc->objDestroyOOBRun();
      LOGV("RS Thread exited");
      return NULL;
diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h
index 0dd90ed..c80fd5a 100644
--- a/libs/rs/rsContext.h
+++ b/libs/rs/rsContext.h
@@ -54,6 +54,7 @@
 
     static pthread_key_t gThreadTLSKey;
     static uint32_t gThreadTLSKeyCount;
+    static uint32_t gGLContextCount;
     static pthread_mutex_t gInitMutex;
 
     struct ScriptTLSStruct {
@@ -215,6 +216,7 @@
     Context();
 
     void initEGL();
+    void deinitEGL();
 
     bool runRootScript();
 
diff --git a/media/java/android/media/AsyncPlayer.java b/media/java/android/media/AsyncPlayer.java
index 35f0409..e1e09b9 100644
--- a/media/java/android/media/AsyncPlayer.java
+++ b/media/java/android/media/AsyncPlayer.java
@@ -19,10 +19,12 @@
 import android.content.Context;
 import android.net.Uri;
 import android.os.PowerManager;
+import android.os.SystemClock;
 import android.util.Log;
 
 import java.io.IOException;
 import java.lang.IllegalStateException;
+import java.util.LinkedList;
 
 /**
  * Plays a series of audio URIs, but does all the hard work on another thread
@@ -31,14 +33,15 @@
 public class AsyncPlayer {
     private static final int PLAY = 1;
     private static final int STOP = 2;
+    private static final boolean mDebug = false;
 
     private static final class Command {
-        Command next;
         int code;
         Context context;
         Uri uri;
         boolean looping;
         int stream;
+        long requestTime;
 
         public String toString() {
             return "{ code=" + code + " looping=" + looping + " stream=" + stream
@@ -46,6 +49,36 @@
         }
     }
 
+    private LinkedList<Command> mCmdQueue = new LinkedList();
+
+    private void startSound(Command cmd) {
+        // Preparing can be slow, so if there is something else
+        // is playing, let it continue until we're done, so there
+        // is less of a glitch.
+        try {
+            if (mDebug) Log.d(mTag, "Starting playback");
+            MediaPlayer player = new MediaPlayer();
+            player.setAudioStreamType(cmd.stream);
+            player.setDataSource(cmd.context, cmd.uri);
+            player.setLooping(cmd.looping);
+            player.prepare();
+            player.start();
+            if (mPlayer != null) {
+                mPlayer.release();
+            }
+            mPlayer = player;
+            long delay = SystemClock.uptimeMillis() - cmd.requestTime;
+            if (delay > 1000) {
+                Log.w(mTag, "Notification sound delayed by " + delay + "msecs");
+            }
+        }
+        catch (IOException e) {
+            Log.w(mTag, "error loading sound for " + cmd.uri, e);
+        } catch (IllegalStateException e) {
+            Log.w(mTag, "IllegalStateException (content provider died?) " + cmd.uri, e);
+        }
+    }
+
     private final class Thread extends java.lang.Thread {
         Thread() {
             super("AsyncPlayer-" + mTag);
@@ -55,41 +88,23 @@
             while (true) {
                 Command cmd = null;
 
-                synchronized (mLock) {
-                    if (mHead != null) {
-                        cmd = mHead;
-                        mHead = cmd.next;
-                        if (mTail == cmd) {
-                            mTail = null;
-                        }
-                    }
+                synchronized (mCmdQueue) {
+                    if (mDebug) Log.d(mTag, "RemoveFirst");
+                    cmd = mCmdQueue.removeFirst();
                 }
 
                 switch (cmd.code) {
                 case PLAY:
-                    try {
-                        // Preparing can be slow, so if there is something else
-                        // is playing, let it continue until we're done, so there
-                        // is less of a glitch.
-                        MediaPlayer player = new MediaPlayer();
-                        player.setAudioStreamType(cmd.stream);
-                        player.setDataSource(cmd.context, cmd.uri);
-                        player.setLooping(cmd.looping);
-                        player.prepare();
-                        player.start();
-                        if (mPlayer != null) {
-                            mPlayer.release();
-                        }
-                        mPlayer = player;
-                    }
-                    catch (IOException e) {
-                        Log.w(mTag, "error loading sound for " + cmd.uri, e);
-                    } catch (IllegalStateException e) {
-                        Log.w(mTag, "IllegalStateException (content provider died?) " + cmd.uri, e);
-                    }
+                    if (mDebug) Log.d(mTag, "PLAY");
+                    startSound(cmd);
                     break;
                 case STOP:
+                    if (mDebug) Log.d(mTag, "STOP");
                     if (mPlayer != null) {
+                        long delay = SystemClock.uptimeMillis() - cmd.requestTime;
+                        if (delay > 1000) {
+                            Log.w(mTag, "Notification stop delayed by " + delay + "msecs");
+                        }
                         mPlayer.stop();
                         mPlayer.release();
                         mPlayer = null;
@@ -99,8 +114,8 @@
                     break;
                 }
 
-                synchronized (mLock) {
-                    if (mHead == null) {
+                synchronized (mCmdQueue) {
+                    if (mCmdQueue.size() == 0) {
                         // nothing left to do, quit
                         // doing this check after we're done prevents the case where they
                         // added it during the operation from spawning two threads and
@@ -115,11 +130,8 @@
     }
 
     private String mTag;
-    private Command mHead;
-    private Command mTail;
     private Thread mThread;
     private MediaPlayer mPlayer;
-    private Object mLock = new Object();
     private PowerManager.WakeLock mWakeLock;
 
     // The current state according to the caller.  Reality lags behind
@@ -154,12 +166,13 @@
      */
     public void play(Context context, Uri uri, boolean looping, int stream) {
         Command cmd = new Command();
+        cmd.requestTime = SystemClock.uptimeMillis();
         cmd.code = PLAY;
         cmd.context = context;
         cmd.uri = uri;
         cmd.looping = looping;
         cmd.stream = stream;
-        synchronized (mLock) {
+        synchronized (mCmdQueue) {
             enqueueLocked(cmd);
             mState = PLAY;
         }
@@ -170,11 +183,12 @@
      * at this point.  Calling this multiple times has no ill effects.
      */
     public void stop() {
-        synchronized (mLock) {
+        synchronized (mCmdQueue) {
             // This check allows stop to be called multiple times without starting
             // a thread that ends up doing nothing.
             if (mState != STOP) {
                 Command cmd = new Command();
+                cmd.requestTime = SystemClock.uptimeMillis();
                 cmd.code = STOP;
                 enqueueLocked(cmd);
                 mState = STOP;
@@ -183,12 +197,7 @@
     }
 
     private void enqueueLocked(Command cmd) {
-        if (mTail == null) {
-            mHead = cmd;
-        } else {
-            mTail.next = cmd;
-        }
-        mTail = cmd;
+        mCmdQueue.add(cmd);
         if (mThread == null) {
             acquireWakeLock();
             mThread = new Thread();
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index 5c4aa79..927c1b3 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -131,6 +131,8 @@
     static final boolean ANIMATE_KEYBOARD_LIGHTS = false;
     
     static final int ANIM_STEPS = 60/4;
+    // Slower animation for autobrightness changes
+    static final int AUTOBRIGHTNESS_ANIM_STEPS = 60;
 
     // These magic numbers are the initial state of the LEDs at boot.  Ideally
     // we should read them from the driver, but our current hardware returns 0
@@ -224,7 +226,7 @@
 
     // could be either static or controllable at runtime
     private static final boolean mSpew = false;
-    private static final boolean mDebugLightSensor = false;
+    private static final boolean mDebugLightSensor = (false || mSpew);
 
     /*
     static PrintStream mLog;
@@ -1925,27 +1927,45 @@
                     Log.d(TAG, "keyboardValue " + keyboardValue);
                 }
 
+                boolean startAnimation = false;
                 if (mScreenBrightnessOverride < 0) {
-                    mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_BACKLIGHT,
-                            lcdValue);
-                }
-                mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_BUTTONS,
-                        buttonValue);
-                mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_KEYBOARD,
-                        keyboardValue);
-
-                // update our animation state
-                if (ANIMATE_SCREEN_LIGHTS) {
-                    mScreenBrightness.curValue = lcdValue;
-                    mScreenBrightness.animating = false;
+                    if (ANIMATE_SCREEN_LIGHTS) {
+                        if (mScreenBrightness.setTargetLocked(lcdValue,
+                                AUTOBRIGHTNESS_ANIM_STEPS, INITIAL_SCREEN_BRIGHTNESS,
+                                (int)mScreenBrightness.curValue)) {
+                            startAnimation = true;
+                        }
+                    } else {
+                        mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_BACKLIGHT,
+                                lcdValue);
+                    }
                 }
                 if (ANIMATE_BUTTON_LIGHTS) {
-                    mButtonBrightness.curValue = buttonValue;
-                    mButtonBrightness.animating = false;
+                    if (mButtonBrightness.setTargetLocked(buttonValue,
+                            AUTOBRIGHTNESS_ANIM_STEPS, INITIAL_BUTTON_BRIGHTNESS,
+                            (int)mButtonBrightness.curValue)) {
+                        startAnimation = true;
+                    }
+                } else {
+                    mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_BUTTONS,
+                            buttonValue);
                 }
                 if (ANIMATE_KEYBOARD_LIGHTS) {
-                    mKeyboardBrightness.curValue = keyboardValue;
-                    mKeyboardBrightness.animating = false;
+                    if (mKeyboardBrightness.setTargetLocked(keyboardValue,
+                            AUTOBRIGHTNESS_ANIM_STEPS, INITIAL_BUTTON_BRIGHTNESS,
+                            (int)mKeyboardBrightness.curValue)) {
+                        startAnimation = true;
+                    }
+                } else {
+                    mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_KEYBOARD,
+                            keyboardValue);
+                }
+                if (startAnimation) {
+                    if (mDebugLightSensor) {
+                        Log.i(TAG, "lightSensorChangedLocked scheduling light animator");
+                    }
+                    mHandler.removeCallbacks(mLightAnimator);
+                    mHandler.post(mLightAnimator);
                 }
             }
         }
@@ -2041,6 +2061,7 @@
         if (mAutoBrightessEnabled != enabled) {
             mAutoBrightessEnabled = enabled;
             // reset computed brightness
+            mLightSensorValue = -1;
             mLightSensorBrightness = -1;
 
             if (mHasHardwareAutoBrightness) {
@@ -2263,14 +2284,17 @@
         if (ANIMATE_SCREEN_LIGHTS) {
             mScreenBrightness.curValue = brightness;
             mScreenBrightness.animating = false;
+            mScreenBrightness.targetValue = -1;
         }
         if (ANIMATE_KEYBOARD_LIGHTS) {
             mKeyboardBrightness.curValue = brightness;
             mKeyboardBrightness.animating = false;
+            mKeyboardBrightness.targetValue = -1;
         }
         if (ANIMATE_BUTTON_LIGHTS) {
             mButtonBrightness.curValue = brightness;
             mButtonBrightness.animating = false;
+            mButtonBrightness.targetValue = -1;
         }
     }