Merge "Optimize text GL setup" into jb-mr2-dev
diff --git a/api/current.txt b/api/current.txt
index 73eed6e..12dc6b4 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2445,6 +2445,7 @@
     method public static android.animation.ObjectAnimator ofObject(java.lang.Object, java.lang.String, android.animation.TypeEvaluator, java.lang.Object...);
     method public static android.animation.ObjectAnimator ofObject(T, android.util.Property<T, V>, android.animation.TypeEvaluator<V>, V...);
     method public static android.animation.ObjectAnimator ofPropertyValuesHolder(java.lang.Object, android.animation.PropertyValuesHolder...);
+    method public void setAutoCancel(boolean);
     method public void setProperty(android.util.Property);
     method public void setPropertyName(java.lang.String);
   }
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index d196392..e1270af 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -28,6 +28,8 @@
 #include <gui/SurfaceComposerClient.h>
 #include <gui/ISurfaceComposer.h>
 
+#include <ui/PixelFormat.h>
+
 #include <SkImageEncoder.h>
 #include <SkBitmap.h>
 #include <SkData.h>
@@ -138,7 +140,7 @@
     ssize_t mapsize = -1;
 
     void const* base = 0;
-    uint32_t w, h, f;
+    uint32_t w, s, h, f;
     size_t size = 0;
 
     ScreenshotClient screenshot;
@@ -147,6 +149,7 @@
         base = screenshot.getPixels();
         w = screenshot.getWidth();
         h = screenshot.getHeight();
+        s = screenshot.getStride();
         f = screenshot.getFormat();
         size = screenshot.getSize();
     } else {
@@ -160,6 +163,7 @@
                     size_t offset = (vinfo.xoffset + vinfo.yoffset*vinfo.xres) * bytespp;
                     w = vinfo.xres;
                     h = vinfo.yres;
+                    s = vinfo.xres;
                     size = w*h*bytespp;
                     mapsize = offset + size;
                     mapbase = mmap(0, mapsize, PROT_READ, MAP_PRIVATE, fb, 0);
@@ -187,7 +191,11 @@
             write(fd, &w, 4);
             write(fd, &h, 4);
             write(fd, &f, 4);
-            write(fd, base, size);
+            size_t Bpp = bytesPerPixel(f);
+            for (size_t y=0 ; y<h ; y++) {
+                write(fd, base, w*Bpp);
+                base = (void *)((char *)base + s*Bpp);
+            }
         }
     }
     close(fd);
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index 0372cb0..173ee73 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -19,7 +19,6 @@
 import android.util.Log;
 import android.util.Property;
 
-import java.lang.reflect.Method;
 import java.util.ArrayList;
 
 /**
@@ -49,6 +48,8 @@
 
     private Property mProperty;
 
+    private boolean mAutoCancel = false;
+
     /**
      * Sets the name of the property that will be animated. This name is used to derive
      * a setter function that will be called to set animated values.
@@ -346,17 +347,83 @@
             // No values yet - this animator is being constructed piecemeal. Init the values with
             // whatever the current propertyName is
             if (mProperty != null) {
-                setValues(PropertyValuesHolder.ofObject(mProperty, (TypeEvaluator)null, values));
+                setValues(PropertyValuesHolder.ofObject(mProperty, (TypeEvaluator) null, values));
             } else {
-                setValues(PropertyValuesHolder.ofObject(mPropertyName, (TypeEvaluator)null, values));
+                setValues(PropertyValuesHolder.ofObject(mPropertyName,
+                        (TypeEvaluator) null, values));
             }
         } else {
             super.setObjectValues(values);
         }
     }
 
+    /**
+     * autoCancel controls whether an ObjectAnimator will be canceled automatically
+     * when any other ObjectAnimator with the same target and properties is started.
+     * Setting this flag may make it easier to run different animators on the same target
+     * object without having to keep track of whether there are conflicting animators that
+     * need to be manually canceled. Canceling animators must have the same exact set of
+     * target properties, in the same order.
+     *
+     * @param cancel Whether future ObjectAnimators with the same target and properties
+     * as this ObjectAnimator will cause this ObjectAnimator to be canceled.
+     */
+    public void setAutoCancel(boolean cancel) {
+        mAutoCancel = cancel;
+    }
+
+    private boolean hasSameTargetAndProperties(Animator anim) {
+        if (anim instanceof ObjectAnimator) {
+            PropertyValuesHolder[] theirValues = ((ObjectAnimator) anim).getValues();
+            if (((ObjectAnimator) anim).getTarget() == mTarget &&
+                    mValues.length == theirValues.length) {
+                for (int i = 0; i < mValues.length; ++i) {
+                    PropertyValuesHolder pvhMine = mValues[i];
+                    PropertyValuesHolder pvhTheirs = theirValues[i];
+                    if (pvhMine.getPropertyName() == null ||
+                            !pvhMine.getPropertyName().equals(pvhTheirs.getPropertyName())) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     public void start() {
+        // See if any of the current active/pending animators need to be canceled
+        AnimationHandler handler = sAnimationHandler.get();
+        if (handler != null) {
+            int numAnims = handler.mAnimations.size();
+            for (int i = numAnims - 1; i >= 0; i--) {
+                if (handler.mAnimations.get(i) instanceof ObjectAnimator) {
+                    ObjectAnimator anim = (ObjectAnimator) handler.mAnimations.get(i);
+                    if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
+                        anim.cancel();
+                    }
+                }
+            }
+            numAnims = handler.mPendingAnimations.size();
+            for (int i = numAnims - 1; i >= 0; i--) {
+                if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator) {
+                    ObjectAnimator anim = (ObjectAnimator) handler.mPendingAnimations.get(i);
+                    if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
+                        anim.cancel();
+                    }
+                }
+            }
+            numAnims = handler.mDelayedAnims.size();
+            for (int i = numAnims - 1; i >= 0; i--) {
+                if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator) {
+                    ObjectAnimator anim = (ObjectAnimator) handler.mDelayedAnims.get(i);
+                    if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
+                        anim.cancel();
+                    }
+                }
+            }
+        }
         if (DBG) {
             Log.d("ObjectAnimator", "Anim target, duration: " + mTarget + ", " + getDuration());
             for (int i = 0; i < mValues.length; ++i) {
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 4a58072..ea605b9 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -83,7 +83,10 @@
 
     // The static sAnimationHandler processes the internal timing loop on which all animations
     // are based
-    private static ThreadLocal<AnimationHandler> sAnimationHandler =
+    /**
+     * @hide
+     */
+    protected static ThreadLocal<AnimationHandler> sAnimationHandler =
             new ThreadLocal<AnimationHandler>();
 
     // The time interpolator to be used if none is set on the animation
@@ -531,22 +534,27 @@
      * animations possible.
      *
      * The handler uses the Choreographer for executing periodic callbacks.
+     *
+     * @hide
      */
-    private static class AnimationHandler implements Runnable {
+    protected static class AnimationHandler implements Runnable {
         // The per-thread list of all active animations
-        private final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();
+        /** @hide */
+        protected final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();
 
         // Used in doAnimationFrame() to avoid concurrent modifications of mAnimations
         private final ArrayList<ValueAnimator> mTmpAnimations = new ArrayList<ValueAnimator>();
 
         // The per-thread set of animations to be started on the next animation frame
-        private final ArrayList<ValueAnimator> mPendingAnimations = new ArrayList<ValueAnimator>();
+        /** @hide */
+        protected final ArrayList<ValueAnimator> mPendingAnimations = new ArrayList<ValueAnimator>();
 
         /**
          * Internal per-thread collections used to avoid set collisions as animations start and end
          * while being processed.
+         * @hide
          */
-        private final ArrayList<ValueAnimator> mDelayedAnims = new ArrayList<ValueAnimator>();
+        protected final ArrayList<ValueAnimator> mDelayedAnims = new ArrayList<ValueAnimator>();
         private final ArrayList<ValueAnimator> mEndingAnims = new ArrayList<ValueAnimator>();
         private final ArrayList<ValueAnimator> mReadyAnims = new ArrayList<ValueAnimator>();
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d57d56a..614ac07 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1136,6 +1136,7 @@
   <java-symbol type="xml" name="power_profile" />
   <java-symbol type="xml" name="time_zones_by_country" />
   <java-symbol type="xml" name="sms_short_codes" />
+  <java-symbol type="xml" name="audio_assets" />
 
   <java-symbol type="raw" name="accessibility_gestures" />
   <java-symbol type="raw" name="incognito_mode_start_page" />
diff --git a/core/res/res/xml/audio_assets.xml b/core/res/res/xml/audio_assets.xml
new file mode 100644
index 0000000..746dbab
--- /dev/null
+++ b/core/res/res/xml/audio_assets.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, 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.
+*/
+-->
+
+<!-- Mapping of UI sound effects to audio assets under /system/media/audio/ui.
+     Modify this file to override default sound assets.
+     Currently only touch sounds can be overridden. Other groups can be added
+     in the future for other UI sounds like camera, lock, dock...
+-->
+
+<audio_assets version="1.0">
+    <group name="touch_sounds">
+        <asset id="FX_KEY_CLICK" file="Effect_Tick.ogg"/>
+        <asset id="FX_FOCUS_NAVIGATION_UP" file="Effect_Tick.ogg"/>
+        <asset id="FX_FOCUS_NAVIGATION_DOWN" file="Effect_Tick.ogg"/>
+        <asset id="FX_FOCUS_NAVIGATION_LEFT" file="Effect_Tick.ogg"/>
+        <asset id="FX_FOCUS_NAVIGATION_RIGHT" file="Effect_Tick.ogg"/>
+        <asset id="FX_KEYPRESS_STANDARD" file="KeypressStandard.ogg"/>
+        <asset id="FX_KEYPRESS_SPACEBAR" file="KeypressSpacebar.ogg"/>
+        <asset id="FX_KEYPRESS_DELETE" file="KeypressDelete.ogg"/>
+        <asset id="FX_KEYPRESS_RETURN" file="KeypressReturn.ogg"/>
+    </group>
+</audio_assets>
diff --git a/core/tests/coretests/src/android/animation/AutoCancelTest.java b/core/tests/coretests/src/android/animation/AutoCancelTest.java
new file mode 100644
index 0000000..b1f88db
--- /dev/null
+++ b/core/tests/coretests/src/android/animation/AutoCancelTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2013 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.animation;
+
+import android.os.Handler;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.HashMap;
+import java.util.concurrent.TimeUnit;
+
+public class AutoCancelTest extends ActivityInstrumentationTestCase2<BasicAnimatorActivity> {
+
+    boolean mAnimX1Canceled = false;
+    boolean mAnimXY1Canceled = false;
+    boolean mAnimX2Canceled = false;
+    boolean mAnimXY2Canceled = false;
+
+    private static final long START_DELAY = 100;
+    private static final long DELAYED_START_DURATION = 200;
+    private static final long FUTURE_TIMEOUT = 1000;
+
+    HashMap<Animator, Boolean> mCanceledMap = new HashMap<Animator, Boolean>();
+
+    public AutoCancelTest() {
+        super(BasicAnimatorActivity.class);
+    }
+
+    ObjectAnimator setupAnimator(long startDelay, String... properties) {
+        ObjectAnimator returnVal;
+        if (properties.length == 1) {
+            returnVal = ObjectAnimator.ofFloat(this, properties[0], 0, 1);
+        } else {
+            PropertyValuesHolder[] pvhArray = new PropertyValuesHolder[properties.length];
+            for (int i = 0; i < properties.length; i++) {
+                pvhArray[i] = PropertyValuesHolder.ofFloat(properties[i], 0, 1);
+            }
+            returnVal = ObjectAnimator.ofPropertyValuesHolder(this, pvhArray);
+        }
+        returnVal.setAutoCancel(true);
+        returnVal.setStartDelay(startDelay);
+        returnVal.addListener(mCanceledListener);
+        return returnVal;
+    }
+
+    private void setupAnimators(long startDelay, boolean startLater, final FutureWaiter future)
+    throws Exception {
+        // Animators to be auto-canceled
+        final ObjectAnimator animX1 = setupAnimator(startDelay, "x");
+        final ObjectAnimator animY1 = setupAnimator(startDelay, "y");
+        final ObjectAnimator animXY1 = setupAnimator(startDelay, "x", "y");
+        final ObjectAnimator animXZ1 = setupAnimator(startDelay, "x", "z");
+
+        animX1.start();
+        animY1.start();
+        animXY1.start();
+        animXZ1.start();
+
+        final ObjectAnimator animX2 = setupAnimator(0, "x");
+        animX2.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                // We expect only animX1 to be canceled at this point
+                if (mCanceledMap.get(animX1) == null ||
+                        mCanceledMap.get(animX1) != true ||
+                        mCanceledMap.get(animY1) != null ||
+                        mCanceledMap.get(animXY1) != null ||
+                        mCanceledMap.get(animXZ1) != null) {
+                    future.set(false);
+                }
+            }
+        });
+
+        final ObjectAnimator animXY2 = setupAnimator(0, "x", "y");
+        animXY2.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                // We expect only animXY1 to be canceled at this point
+                if (mCanceledMap.get(animXY1) == null ||
+                        mCanceledMap.get(animXY1) != true ||
+                        mCanceledMap.get(animY1) != null ||
+                        mCanceledMap.get(animXZ1) != null) {
+                    future.set(false);
+                }
+
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                // Release future if not done already via failures during start
+                future.release();
+            }
+        });
+
+        if (startLater) {
+            Handler handler = new Handler();
+            handler.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    animX2.start();
+                    animXY2.start();
+                }
+            }, DELAYED_START_DURATION);
+        } else {
+            animX2.start();
+            animXY2.start();
+        }
+    }
+
+    @SmallTest
+    public void testAutoCancel() throws Exception {
+        final FutureWaiter future = new FutureWaiter();
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    setupAnimators(0, false, future);
+                } catch (Exception e) {
+                    future.setException(e);
+                }
+            }
+        });
+        assertTrue(future.get(FUTURE_TIMEOUT, TimeUnit.MILLISECONDS));
+    }
+
+    @SmallTest
+    public void testAutoCancelDelayed() throws Exception {
+        final FutureWaiter future = new FutureWaiter();
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    setupAnimators(START_DELAY, false, future);
+                } catch (Exception e) {
+                    future.setException(e);
+                }
+            }
+        });
+        assertTrue(future.get(FUTURE_TIMEOUT, TimeUnit.MILLISECONDS));
+    }
+
+    @SmallTest
+    public void testAutoCancelTestLater() throws Exception {
+        final FutureWaiter future = new FutureWaiter();
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    setupAnimators(0, true, future);
+                } catch (Exception e) {
+                    future.setException(e);
+                }
+            }
+        });
+        assertTrue(future.get(FUTURE_TIMEOUT, TimeUnit.MILLISECONDS));
+    }
+
+    @SmallTest
+    public void testAutoCancelDelayedTestLater() throws Exception {
+        final FutureWaiter future = new FutureWaiter();
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    setupAnimators(START_DELAY, true, future);
+                } catch (Exception e) {
+                    future.setException(e);
+                }
+            }
+        });
+        assertTrue(future.get(FUTURE_TIMEOUT, TimeUnit.MILLISECONDS));
+    }
+
+    private AnimatorListenerAdapter mCanceledListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            mCanceledMap.put(animation, true);
+        }
+    };
+
+    public void setX(float x) {}
+
+    public void setY(float y) {}
+
+    public void setZ(float z) {}
+}
+
+
diff --git a/core/tests/coretests/src/android/animation/FutureWaiter.java b/core/tests/coretests/src/android/animation/FutureWaiter.java
index 320a1c2..0c65e20 100644
--- a/core/tests/coretests/src/android/animation/FutureWaiter.java
+++ b/core/tests/coretests/src/android/animation/FutureWaiter.java
@@ -23,14 +23,21 @@
  * {@link com.google.common.util.concurrent.AbstractFuture#set(Object)} method internally. It
  * also exposes the protected {@link AbstractFuture#setException(Throwable)} method.
  */
-public class FutureWaiter extends AbstractFuture<Void> {
+public class FutureWaiter extends AbstractFuture<Boolean> {
 
     /**
      * Release the Future currently waiting on
      * {@link com.google.common.util.concurrent.AbstractFuture#get()}.
      */
     public void release() {
-        super.set(null);
+        super.set(true);
+    }
+
+    /**
+     * Used to indicate failure (when the result value is false).
+     */
+    public void set(boolean result) {
+        super.set(result);
     }
 
     @Override
diff --git a/docs/html/training/notify-user/build-notification.jd b/docs/html/training/notify-user/build-notification.jd
index ba66028..80f2cd5 100644
--- a/docs/html/training/notify-user/build-notification.jd
+++ b/docs/html/training/notify-user/build-notification.jd
@@ -149,12 +149,14 @@
 <p>For example:</p>
 
 <pre>
+NotificationCompat.Builder mBuilder;
+...
 // Sets an ID for the notification
 int mNotificationId = 001;
 // Gets an instance of the NotificationManager service
 NotificationManager mNotifyMgr = 
         (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
 // Builds the notification and issues it.
-mNotifyMgr.notify(mNotificationId, builder.build());
+mNotifyMgr.notify(mNotificationId, mBuilder.build());
 </pre>
 
diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp
index 1ace42f..7907224 100644
--- a/libs/hwui/DeferredDisplayList.cpp
+++ b/libs/hwui/DeferredDisplayList.cpp
@@ -78,7 +78,7 @@
             renderer.restoreDisplayState(op->state, kStateDeferFlag_Draw);
 
 #if DEBUG_DISPLAY_LIST_OPS_AS_EVENTS
-            renderer.eventMark(strlen(op->name()), op->name());
+            renderer.eventMark(op->name());
 #endif
             status |= op->applyDraw(renderer, dirty, 0);
             logBuffer.writeCommand(0, op->name());
@@ -134,7 +134,6 @@
     virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty) {
         DEFER_LOGD("batch %p restoring to count %d", this, mRestoreCount);
         renderer.restoreToCount(mRestoreCount);
-
         return DrawGlInfo::kStatusDone;
     }
 
@@ -373,9 +372,10 @@
     DEFER_LOGD("--flushing");
     renderer.eventMark("Flush");
 
+    DrawModifiers restoreDrawModifiers = renderer.getDrawModifiers();
     renderer.restoreToCount(1);
     status |= replayBatchList(mBatches, renderer, dirty);
-    renderer.resetDrawModifiers();
+    renderer.setDrawModifiers(restoreDrawModifiers);
 
     DEFER_LOGD("--flush complete, returning %x", status);
 
diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h
index 8e908fa..cb9da8f 100644
--- a/libs/hwui/DeferredDisplayList.h
+++ b/libs/hwui/DeferredDisplayList.h
@@ -72,7 +72,7 @@
     void addDrawOp(OpenGLRenderer& renderer, DrawOp* op);
 
 private:
-    /*
+    /**
      * Resets the batching back-pointers, creating a barrier in the operation stream so that no ops
      * added in the future will be inserted into a batch that already exist.
      */
@@ -88,9 +88,10 @@
     int getStateOpDeferFlags() const;
     int getDrawOpDeferFlags() const;
 
-    /*
-     *
-     * at defer time, stores the savecount of save/saveLayer ops that were 
+    /**
+     * At defer time, stores the *defer time* savecount of save/saveLayer ops that were deferred, so
+     * that when an associated restoreToCount is deferred, it can be recorded as a
+     * RestoreToCountBatch
      */
     Vector<int> mSaveStack;
     int mComplexClipStackStart;
diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp
index 257c875..55ddd17 100644
--- a/libs/hwui/DisplayList.cpp
+++ b/libs/hwui/DisplayList.cpp
@@ -443,7 +443,7 @@
         : mReplayStruct(replayStruct), mLevel(level) {}
     inline void operator()(DisplayListOp* operation, int saveCount) {
 #if DEBUG_DISPLAY_LIST_OPS_AS_EVENTS
-        replayStruct.mRenderer.eventMark(operation->name());
+        mReplayStruct.mRenderer.eventMark(operation->name());
 #endif
         operation->replay(mReplayStruct, saveCount, mLevel);
     }
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 4561c61..4f2db69 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -284,8 +284,13 @@
 
     virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level) {
         // NOTE: don't bother with actual saveLayer, instead issuing it at flush time
-        int newSaveCount = deferStruct.mRenderer.save(mFlags);
+        int newSaveCount = deferStruct.mRenderer.getSaveCount();
         deferStruct.mDeferredList.addSaveLayer(deferStruct.mRenderer, this, newSaveCount);
+
+        // NOTE: don't issue full saveLayer, since that has side effects/is costly. instead just
+        // setup the snapshot for deferral, and re-issue the op at flush time
+        deferStruct.mRenderer.saveLayerDeferred(mArea.left, mArea.top, mArea.right, mArea.bottom,
+                mAlpha, mMode, mFlags);
     }
 
     virtual void applyState(OpenGLRenderer& renderer, int saveCount) {
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index e7dfa66..bc28d65 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -112,7 +112,10 @@
 
 OpenGLRenderer::OpenGLRenderer():
         mCaches(Caches::getInstance()), mExtensions(Extensions::getInstance()) {
-    resetDrawModifiers();
+    mDrawModifiers.mShader = NULL;
+    mDrawModifiers.mColorFilter = NULL;
+    mDrawModifiers.mHasShadow = false;
+    mDrawModifiers.mHasDrawFilter = false;
 
     memcpy(mMeshVertices, gMeshVertices, sizeof(gMeshVertices));
 
@@ -643,6 +646,63 @@
     return count;
 }
 
+void OpenGLRenderer::calculateLayerBoundsAndClip(Rect& bounds, Rect& clip, bool fboLayer) {
+    const Rect untransformedBounds(bounds);
+
+    currentTransform().mapRect(bounds);
+
+    // Layers only make sense if they are in the framebuffer's bounds
+    if (bounds.intersect(*mSnapshot->clipRect)) {
+        // We cannot work with sub-pixels in this case
+        bounds.snapToPixelBoundaries();
+
+        // When the layer is not an FBO, we may use glCopyTexImage so we
+        // need to make sure the layer does not extend outside the bounds
+        // of the framebuffer
+        if (!bounds.intersect(mSnapshot->previous->viewport)) {
+            bounds.setEmpty();
+        } else if (fboLayer) {
+            clip.set(bounds);
+            mat4 inverse;
+            inverse.loadInverse(currentTransform());
+            inverse.mapRect(clip);
+            clip.snapToPixelBoundaries();
+            if (clip.intersect(untransformedBounds)) {
+                clip.translate(-untransformedBounds.left, -untransformedBounds.top);
+                bounds.set(untransformedBounds);
+            } else {
+                clip.setEmpty();
+            }
+        }
+    } else {
+        bounds.setEmpty();
+    }
+}
+
+int OpenGLRenderer::saveLayerDeferred(float left, float top, float right, float bottom,
+        int alpha, SkXfermode::Mode mode, int flags) {
+    const GLuint previousFbo = mSnapshot->fbo;
+    const int count = saveSnapshot(flags);
+
+    if (!mSnapshot->isIgnored() && (flags & SkCanvas::kClipToLayer_SaveFlag)) {
+        // initialize the snapshot as though it almost represents an FBO layer so deferred draw
+        // operations will be able to store and restore the current clip and transform info, and
+        // quick rejection will be correct (for display lists)
+
+        Rect bounds(left, top, right, bottom);
+        Rect clip;
+        calculateLayerBoundsAndClip(bounds, clip, true);
+
+        if (!bounds.isEmpty() && !clip.isEmpty()) {
+            mSnapshot->resetTransform(-bounds.left, -bounds.top, 0.0f);
+            mSnapshot->resetClip(clip.left, clip.top, clip.right, clip.bottom);
+        }
+    }
+
+    return count;
+}
+
+
 /**
  * Layers are viewed by Skia are slightly different than layers in image editing
  * programs (for instance.) When a layer is created, previously created layers
@@ -704,35 +764,7 @@
     // Window coordinates of the layer
     Rect clip;
     Rect bounds(left, top, right, bottom);
-    Rect untransformedBounds(bounds);
-    currentTransform().mapRect(bounds);
-
-    // Layers only make sense if they are in the framebuffer's bounds
-    if (bounds.intersect(*mSnapshot->clipRect)) {
-        // We cannot work with sub-pixels in this case
-        bounds.snapToPixelBoundaries();
-
-        // When the layer is not an FBO, we may use glCopyTexImage so we
-        // need to make sure the layer does not extend outside the bounds
-        // of the framebuffer
-        if (!bounds.intersect(mSnapshot->previous->viewport)) {
-            bounds.setEmpty();
-        } else if (fboLayer) {
-            clip.set(bounds);
-            mat4 inverse;
-            inverse.loadInverse(currentTransform());
-            inverse.mapRect(clip);
-            clip.snapToPixelBoundaries();
-            if (clip.intersect(untransformedBounds)) {
-                clip.translate(-left, -top);
-                bounds.set(untransformedBounds);
-            } else {
-                clip.setEmpty();
-            }
-        }
-    } else {
-        bounds.setEmpty();
-    }
+    calculateLayerBoundsAndClip(bounds, clip, fboLayer);
 
     if (bounds.isEmpty() || bounds.getWidth() > mCaches.maxTextureSize ||
             bounds.getHeight() > mCaches.maxTextureSize ||
@@ -900,7 +932,7 @@
 }
 
 void OpenGLRenderer::drawTextureLayer(Layer* layer, const Rect& rect) {
-    float alpha = layer->getAlpha() / 255.0f;
+    float alpha = layer->getAlpha() / 255.0f * mSnapshot->alpha;
 
     setupDraw();
     if (layer->getRenderTarget() == GL_TEXTURE_2D) {
@@ -964,9 +996,10 @@
             layer->setFilter(GL_LINEAR, true);
         }
 
+        float alpha = layer->getAlpha() / 255.0f * mSnapshot->alpha;
+        bool blend = layer->isBlend() || alpha < 1.0f;
         drawTextureMesh(x, y, x + rect.getWidth(), y + rect.getHeight(),
-                layer->getTexture(), layer->getAlpha() / 255.0f,
-                layer->getMode(), layer->isBlend(),
+                layer->getTexture(), alpha, layer->getMode(), blend,
                 &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0],
                 GL_TRIANGLE_STRIP, gMeshCount, swap, swap || simpleTransform);
 
@@ -1001,7 +1034,7 @@
             rects = safeRegion.getArray(&count);
         }
 
-        const float alpha = layer->getAlpha() / 255.0f;
+        const float alpha = layer->getAlpha() / 255.0f * mSnapshot->alpha;
         const float texX = 1.0f / float(layer->getWidth());
         const float texY = 1.0f / float(layer->getHeight());
         const float height = rect.getHeight();
@@ -1201,13 +1234,6 @@
 // State Deferral
 ///////////////////////////////////////////////////////////////////////////////
 
-void OpenGLRenderer::resetDrawModifiers() {
-    mDrawModifiers.mShader = NULL;
-    mDrawModifiers.mColorFilter = NULL;
-    mDrawModifiers.mHasShadow = false;
-    mDrawModifiers.mHasDrawFilter = false;
-}
-
 bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDeferFlags) {
     const Rect& currentClip = *(mSnapshot->clipRect);
     const mat4& currentMatrix = *(mSnapshot->transform);
@@ -1246,7 +1272,7 @@
         mSnapshot->alpha = state.mAlpha;
     }
 
-    if (!state.mClip.isEmpty()) { //stateDeferFlags & kStateDeferFlag_Clip) {
+    if (!state.mClip.isEmpty()) {
         mSnapshot->setClip(state.mClip.left, state.mClip.top, state.mClip.right, state.mClip.bottom);
         dirtyClip();
     }
@@ -1805,7 +1831,7 @@
     // All the usual checks and setup operations (quickReject, setupDraw, etc.)
     // will be performed by the display list itself
     if (displayList && displayList->isRenderable()) {
-        if (true || CC_UNLIKELY(mCaches.drawDeferDisabled)) { // NOTE: temporary workaround
+        if (CC_UNLIKELY(mCaches.drawDeferDisabled)) {
             ReplayStateStruct replayStruct(*this, dirty, replayFlags);
             displayList->replay(replayStruct, 0);
             return replayStruct.mDrawGlStatus;
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 554ccb0..71bd6bb 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -206,6 +206,9 @@
     virtual int saveLayer(float left, float top, float right, float bottom,
             int alpha, SkXfermode::Mode mode, int flags);
 
+    int saveLayerDeferred(float left, float top, float right, float bottom,
+            int alpha, SkXfermode::Mode mode, int flags);
+
     virtual void translate(float dx, float dy);
     virtual void rotate(float degrees);
     virtual void scale(float sx, float sy);
@@ -274,10 +277,12 @@
 
     SkPaint* filterPaint(SkPaint* paint);
 
-    void resetDrawModifiers();
     bool storeDisplayState(DeferredDisplayState& state, int stateDeferFlags);
     void restoreDisplayState(const DeferredDisplayState& state, int stateDeferFlags);
 
+    const DrawModifiers& getDrawModifiers() { return mDrawModifiers; }
+    void setDrawModifiers(const DrawModifiers& drawModifiers) { mDrawModifiers = drawModifiers; }
+
     // TODO: what does this mean? no perspective? no rotate?
     ANDROID_API bool isCurrentTransformSimple() {
         return mSnapshot->transform->isSimple();
@@ -539,6 +544,11 @@
     bool quickRejectPreStroke(float left, float top, float right, float bottom, SkPaint* paint);
 
     /**
+     * given the local bounds of the layer, calculates ...
+     */
+    void calculateLayerBoundsAndClip(Rect& bounds, Rect& clip, bool fboLayer);
+
+    /**
      * Creates a new layer stored in the specified snapshot.
      *
      * @param snapshot The snapshot associated with the new layer
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index f0a5c28..3e4d6f3 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -42,6 +42,8 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
 import android.media.MediaPlayer.OnCompletionListener;
 import android.media.MediaPlayer.OnErrorListener;
@@ -70,10 +72,14 @@
 import android.view.VolumePanel;
 
 import com.android.internal.telephony.ITelephony;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.HashMap;
@@ -197,28 +203,12 @@
 
     /* Sound effect file names  */
     private static final String SOUND_EFFECTS_PATH = "/media/audio/ui/";
-    private static final String[] SOUND_EFFECT_FILES = new String[] {
-        "Effect_Tick.ogg",
-        "KeypressStandard.ogg",
-        "KeypressSpacebar.ogg",
-        "KeypressDelete.ogg",
-        "KeypressReturn.ogg"
-    };
+    private static final List<String> SOUND_EFFECT_FILES = new ArrayList<String>();
 
     /* Sound effect file name mapping sound effect id (AudioManager.FX_xxx) to
      * file index in SOUND_EFFECT_FILES[] (first column) and indicating if effect
      * uses soundpool (second column) */
-    private final int[][] SOUND_EFFECT_FILES_MAP = new int[][] {
-        {0, -1},  // FX_KEY_CLICK
-        {0, -1},  // FX_FOCUS_NAVIGATION_UP
-        {0, -1},  // FX_FOCUS_NAVIGATION_DOWN
-        {0, -1},  // FX_FOCUS_NAVIGATION_LEFT
-        {0, -1},  // FX_FOCUS_NAVIGATION_RIGHT
-        {1, -1},  // FX_KEYPRESS_STANDARD
-        {2, -1},  // FX_KEYPRESS_SPACEBAR
-        {3, -1},  // FX_FOCUS_DELETE
-        {4, -1}   // FX_FOCUS_RETURN
-    };
+    private final int[][] SOUND_EFFECT_FILES_MAP = new int[AudioManager.NUM_SOUND_EFFECTS][2];
 
    /** @hide Maximum volume index values for audio streams */
     private final int[] MAX_STREAM_VOLUME = new int[] {
@@ -1634,6 +1624,99 @@
         return mMode;
     }
 
+    //==========================================================================================
+    // Sound Effects
+    //==========================================================================================
+
+    private static final String TAG_AUDIO_ASSETS = "audio_assets";
+    private static final String ATTR_VERSION = "version";
+    private static final String TAG_GROUP = "group";
+    private static final String ATTR_GROUP_NAME = "name";
+    private static final String TAG_ASSET = "asset";
+    private static final String ATTR_ASSET_ID = "id";
+    private static final String ATTR_ASSET_FILE = "file";
+
+    private static final String ASSET_FILE_VERSION = "1.0";
+    private static final String GROUP_TOUCH_SOUNDS = "touch_sounds";
+
+    private void loadTouchSoundAssetDefaults() {
+        SOUND_EFFECT_FILES.add("Effect_Tick.ogg");
+        for (int i = 0; i < AudioManager.NUM_SOUND_EFFECTS; i++) {
+            SOUND_EFFECT_FILES_MAP[i][0] = 0;
+            SOUND_EFFECT_FILES_MAP[i][1] = -1;
+        }
+    }
+
+    private void loadTouchSoundAssets() {
+        XmlResourceParser parser = null;
+
+        loadTouchSoundAssetDefaults();
+
+        try {
+            parser = mContext.getResources().getXml(com.android.internal.R.xml.audio_assets);
+
+            XmlUtils.beginDocument(parser, TAG_AUDIO_ASSETS);
+            String version = parser.getAttributeValue(null, ATTR_VERSION);
+            boolean inTouchSoundsGroup = false;
+
+            if (ASSET_FILE_VERSION.equals(version)) {
+                while (true) {
+                    XmlUtils.nextElement(parser);
+                    String element = parser.getName();
+                    if (element == null) {
+                        break;
+                    }
+                    if (element.equals(TAG_GROUP)) {
+                        String name = parser.getAttributeValue(null, ATTR_GROUP_NAME);
+                        if (GROUP_TOUCH_SOUNDS.equals(name)) {
+                            inTouchSoundsGroup = true;
+                            break;
+                        }
+                    }
+                }
+                while (inTouchSoundsGroup) {
+                    XmlUtils.nextElement(parser);
+                    String element = parser.getName();
+                    if (element == null) {
+                        break;
+                    }
+                    if (element.equals(TAG_ASSET)) {
+                        String id = parser.getAttributeValue(null, ATTR_ASSET_ID);
+                        String file = parser.getAttributeValue(null, ATTR_ASSET_FILE);
+                        int fx;
+
+                        try {
+                            Field field = AudioManager.class.getField(id);
+                            fx = field.getInt(null);
+                        } catch (Exception e) {
+                            Log.w(TAG, "Invalid touch sound ID: "+id);
+                            continue;
+                        }
+
+                        int i = SOUND_EFFECT_FILES.indexOf(file);
+                        if (i == -1) {
+                            i = SOUND_EFFECT_FILES.size();
+                            SOUND_EFFECT_FILES.add(file);
+                        }
+                        SOUND_EFFECT_FILES_MAP[fx][0] = i;
+                    } else {
+                        break;
+                    }
+                }
+            }
+        } catch (Resources.NotFoundException e) {
+            Log.w(TAG, "audio assets file not found", e);
+        } catch (XmlPullParserException e) {
+            Log.w(TAG, "XML parser exception reading touch sound assets", e);
+        } catch (IOException e) {
+            Log.w(TAG, "I/O exception reading touch sound assets", e);
+        } finally {
+            if (parser != null) {
+                parser.close();
+            }
+        }
+    }
+
     /** @see AudioManager#playSoundEffect(int) */
     public void playSoundEffect(int effectType) {
         sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SENDMSG_NOOP,
@@ -1654,6 +1737,8 @@
     public boolean loadSoundEffects() {
         int status;
 
+        loadTouchSoundAssets();
+
         synchronized (mSoundEffectsLock) {
             if (!mBootCompleted) {
                 Log.w(TAG, "loadSoundEffects() called before boot complete");
@@ -1692,8 +1777,8 @@
              * Once loaded, the value in poolId is the sample ID and the same
              * sample can be reused for another effect using the same file.
              */
-            int[] poolId = new int[SOUND_EFFECT_FILES.length];
-            for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.length; fileIdx++) {
+            int[] poolId = new int[SOUND_EFFECT_FILES.size()];
+            for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.size(); fileIdx++) {
                 poolId[fileIdx] = -1;
             }
             /*
@@ -1711,7 +1796,7 @@
                 if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == -1) {
                     String filePath = Environment.getRootDirectory()
                             + SOUND_EFFECTS_PATH
-                            + SOUND_EFFECT_FILES[SOUND_EFFECT_FILES_MAP[effect][0]];
+                            + SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effect][0]);
                     int sampleId = mSoundPool.load(filePath, 0);
                     if (sampleId <= 0) {
                         Log.w(TAG, "Soundpool could not load file: "+filePath);
@@ -1776,8 +1861,8 @@
             mAudioHandler.removeMessages(MSG_LOAD_SOUND_EFFECTS);
             mAudioHandler.removeMessages(MSG_PLAY_SOUND_EFFECT);
 
-            int[] poolId = new int[SOUND_EFFECT_FILES.length];
-            for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.length; fileIdx++) {
+            int[] poolId = new int[SOUND_EFFECT_FILES.size()];
+            for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.size(); fileIdx++) {
                 poolId[fileIdx] = 0;
             }
 
@@ -3279,7 +3364,8 @@
                 } else {
                     MediaPlayer mediaPlayer = new MediaPlayer();
                     try {
-                        String filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH + SOUND_EFFECT_FILES[SOUND_EFFECT_FILES_MAP[effectType][0]];
+                        String filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH +
+                                    SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]);
                         mediaPlayer.setDataSource(filePath);
                         mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM);
                         mediaPlayer.prepare();
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
index 46f6430..4885407 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
@@ -147,6 +147,16 @@
         // In other words, mUserId should never change - hence it's marked final.
         mUserId = mLockPatternUtils.getCurrentUser();
 
+        DevicePolicyManager dpm =
+                (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        if (dpm != null) {
+            mDisabledFeatures = getDisabledFeatures(dpm);
+            mCameraDisabled = dpm.getCameraDisabled(null);
+        }
+
+        mSafeModeEnabled = LockPatternUtils.isSafeModeEnabled();
+
+        // These need to be created with the user context...
         Context userContext = null;
         try {
             final String packageName = "system";
@@ -159,29 +169,13 @@
             userContext = context;
         }
 
-        // These need to be created with the user context...
         mAppWidgetHost = new AppWidgetHost(userContext, APPWIDGET_HOST_ID, mOnClickHandler,
                 Looper.myLooper());
+
+        cleanupAppWidgetIds();
+
         mAppWidgetManager = AppWidgetManager.getInstance(userContext);
 
-        cleanupAppWidgetIds();
-
-        mSecurityModel = new KeyguardSecurityModel(context);
-
-        mViewStateManager = new KeyguardViewStateManager(this);
-
-        DevicePolicyManager dpm =
-                (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
-        if (dpm != null) {
-            mDisabledFeatures = getDisabledFeatures(dpm);
-            mCameraDisabled = dpm.getCameraDisabled(null);
-        }
-
-        mSafeModeEnabled = LockPatternUtils.isSafeModeEnabled();
-
-        cleanupAppWidgetIds();
-
-        mAppWidgetManager = AppWidgetManager.getInstance(mContext);
         mSecurityModel = new KeyguardSecurityModel(context);
 
         mViewStateManager = new KeyguardViewStateManager(this);