Merge "Add GC thread to RSGL." into jb-mr2-dev
diff --git a/api/current.txt b/api/current.txt
index 1e1f01c..4236eae 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -18442,6 +18442,7 @@
   }
 
   protected static abstract interface ContactsContract.ContactsColumns {
+    field public static final java.lang.String CONTACT_LAST_UPDATED_TIMESTAMP = "contact_last_updated_timestamp";
     field public static final java.lang.String DISPLAY_NAME = "display_name";
     field public static final java.lang.String HAS_PHONE_NUMBER = "has_phone_number";
     field public static final java.lang.String IN_VISIBLE_GROUP = "in_visible_group";
@@ -18506,6 +18507,16 @@
     field public static final java.lang.String TIMES_USED = "times_used";
   }
 
+  public static final class ContactsContract.DeletedContacts implements android.provider.ContactsContract.DeletedContactsColumns {
+    field public static final android.net.Uri CONTENT_URI;
+    field public static final long DAYS_KEPT_MILLISECONDS = 2592000000L; // 0x9a7ec800L
+  }
+
+  protected static abstract interface ContactsContract.DeletedContactsColumns {
+    field public static final java.lang.String CONTACT_DELETED_TIMESTAMP = "contact_deleted_timestamp";
+    field public static final java.lang.String CONTACT_ID = "contact_id";
+  }
+
   public static final class ContactsContract.Directory implements android.provider.BaseColumns {
     method public static void notifyDirectoryChange(android.content.ContentResolver);
     field public static final java.lang.String ACCOUNT_NAME = "accountName";
@@ -18585,6 +18596,7 @@
   public static final class ContactsContract.Intents {
     ctor public ContactsContract.Intents();
     field public static final java.lang.String ATTACH_IMAGE = "com.android.contacts.action.ATTACH_IMAGE";
+    field public static final java.lang.String CONTACTS_DATABASE_CREATED = "android.provider.Contacts.DATABASE_CREATED";
     field public static final java.lang.String EXTRA_CREATE_DESCRIPTION = "com.android.contacts.action.CREATE_DESCRIPTION";
     field public static final java.lang.String EXTRA_FORCE_CREATE = "com.android.contacts.action.FORCE_CREATE";
     field public static final java.lang.String INVITE_CONTACT = "com.android.contacts.action.INVITE_CONTACT";
@@ -19649,8 +19661,7 @@
     method public int getUsage();
     method public void ioReceive();
     method public void ioSend();
-    method public synchronized void resize(int);
-    method public synchronized void resize(int, int);
+    method public deprecated synchronized void resize(int);
     method public void setFromFieldPacker(int, android.renderscript.FieldPacker);
     method public void setFromFieldPacker(int, int, android.renderscript.FieldPacker);
     method public void setSurface(android.view.Surface);
@@ -27589,8 +27600,6 @@
     method public synchronized int getMinimumFontSize();
     method public synchronized int getMinimumLogicalFontSize();
     method public deprecated synchronized android.webkit.WebSettings.PluginState getPluginState();
-    method public deprecated synchronized boolean getPluginsEnabled();
-    method public deprecated synchronized java.lang.String getPluginsPath();
     method public synchronized java.lang.String getSansSerifFontFamily();
     method public boolean getSaveFormData();
     method public deprecated boolean getSavePassword();
@@ -27636,8 +27645,6 @@
     method public synchronized void setMinimumLogicalFontSize(int);
     method public void setNeedInitialFocus(boolean);
     method public deprecated synchronized void setPluginState(android.webkit.WebSettings.PluginState);
-    method public deprecated synchronized void setPluginsEnabled(boolean);
-    method public deprecated synchronized void setPluginsPath(java.lang.String);
     method public deprecated synchronized void setRenderPriority(android.webkit.WebSettings.RenderPriority);
     method public synchronized void setSansSerifFontFamily(java.lang.String);
     method public void setSaveFormData(boolean);
diff --git a/cmds/app_process/Android.mk b/cmds/app_process/Android.mk
index b39c335..b9afe40 100644
--- a/cmds/app_process/Android.mk
+++ b/cmds/app_process/Android.mk
@@ -7,6 +7,7 @@
 LOCAL_SHARED_LIBRARIES := \
 	libcutils \
 	libutils \
+	liblog \
 	libbinder \
 	libandroid_runtime
 
@@ -27,6 +28,7 @@
 LOCAL_SHARED_LIBRARIES := \
 	libcutils \
 	libutils \
+	liblog \
 	libbinder \
 	libandroid_runtime
 
diff --git a/cmds/bootanimation/Android.mk b/cmds/bootanimation/Android.mk
index 8c46b21..d5ff84e 100644
--- a/cmds/bootanimation/Android.mk
+++ b/cmds/bootanimation/Android.mk
@@ -9,6 +9,7 @@
 
 LOCAL_SHARED_LIBRARIES := \
 	libcutils \
+	liblog \
 	libandroidfw \
 	libutils \
 	libbinder \
diff --git a/cmds/input/src/com/android/commands/input/Input.java b/cmds/input/src/com/android/commands/input/Input.java
index cdbc405..e43501c 100644
--- a/cmds/input/src/com/android/commands/input/Input.java
+++ b/cmds/input/src/com/android/commands/input/Input.java
@@ -75,11 +75,14 @@
                             Float.parseFloat(args[3]), Float.parseFloat(args[4]), -1);
                     return;
                 }
-            } else if (command.equals("touchscreen") || command.equals("touchpad")) {
+            } else if (command.equals("touchscreen") || command.equals("touchpad")
+                    || command.equals("touchnavigation")) {
                 // determine input source
                 int inputSource = InputDevice.SOURCE_TOUCHSCREEN;
                 if (command.equals("touchpad")) {
                     inputSource = InputDevice.SOURCE_TOUCHPAD;
+                } else if (command.equals("touchnavigation")) {
+                    inputSource = InputDevice.SOURCE_TOUCH_NAVIGATION;
                 }
                 // determine subcommand
                 if (args.length > 1) {
@@ -247,8 +250,9 @@
         System.err.println("usage: input ...");
         System.err.println("       input text <string>");
         System.err.println("       input keyevent <key code number or name>");
-        System.err.println("       input [touchscreen|touchpad] tap <x> <y>");
-        System.err.println("       input [touchscreen|touchpad] swipe <x1> <y1> <x2> <y2> [duration(ms)]");
+        System.err.println("       input [touchscreen|touchpad|touchnavigation] tap <x> <y>");
+        System.err.println("       input [touchscreen|touchpad|touchnavigation] swipe "
+                + "<x1> <y1> <x2> <y2> [duration(ms)]");
         System.err.println("       input trackball press");
         System.err.println("       input trackball roll <dx> <dy>");
     }
diff --git a/cmds/system_server/Android.mk b/cmds/system_server/Android.mk
index ad537977..3083e31 100644
--- a/cmds/system_server/Android.mk
+++ b/cmds/system_server/Android.mk
@@ -7,7 +7,8 @@
 LOCAL_SHARED_LIBRARIES := \
 	libutils \
 	libbinder \
-	libsystem_server 
+	libsystem_server \
+	liblog
 
 LOCAL_C_INCLUDES := \
 	$(JNI_H_INCLUDE)
@@ -17,4 +18,3 @@
 include $(BUILD_EXECUTABLE)
 
 include $(LOCAL_PATH)/library/Android.mk
-
diff --git a/cmds/system_server/library/Android.mk b/cmds/system_server/library/Android.mk
index c42424c..d78474e 100644
--- a/cmds/system_server/library/Android.mk
+++ b/cmds/system_server/library/Android.mk
@@ -16,10 +16,11 @@
 	libandroid_runtime \
 	libsensorservice \
 	libsurfaceflinger \
-    libinput \
+	libinput \
 	libutils \
 	libbinder \
-	libcutils
+	libcutils \
+	liblog
 
 LOCAL_MODULE:= libsystem_server
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 68a2397..e6ce963 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4322,6 +4322,10 @@
             GLUtils.setTracingLevel(1);
         }
 
+        // Allow application-generated systrace messages if we're debuggable.
+        boolean appTracingAllowed = (data.appInfo.flags&ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+        Trace.setAppTracingAllowed(appTracingAllowed);
+
         /**
          * Initialize the default http proxy in this process for the reasons we set the time zone.
          */
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 27ed6b6..310b12c 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -19,41 +19,55 @@
 import android.util.Log;
 
 /**
- * Writes trace events to the kernel trace buffer.  These trace events can be
- * collected using the "atrace" program for offline analysis.
+ * Writes trace events to the system trace buffer.  These trace events can be
+ * collected and visualized using the Systrace tool.
  *
  * This tracing mechanism is independent of the method tracing mechanism
  * offered by {@link Debug#startMethodTracing}.  In particular, it enables
- * tracing of events that occur across processes.
+ * tracing of events that occur across multiple processes.
  *
  * @hide
  */
 public final class Trace {
+    /*
+     * Writes trace events to the kernel trace buffer.  These trace events can be
+     * collected using the "atrace" program for offline analysis.
+     */
+
     private static final String TAG = "Trace";
 
     // These tags must be kept in sync with system/core/include/cutils/trace.h.
+    /** @hide */
     public static final long TRACE_TAG_NEVER = 0;
+    /** @hide */
     public static final long TRACE_TAG_ALWAYS = 1L << 0;
+    /** @hide */
     public static final long TRACE_TAG_GRAPHICS = 1L << 1;
+    /** @hide */
     public static final long TRACE_TAG_INPUT = 1L << 2;
+    /** @hide */
     public static final long TRACE_TAG_VIEW = 1L << 3;
+    /** @hide */
     public static final long TRACE_TAG_WEBVIEW = 1L << 4;
+    /** @hide */
     public static final long TRACE_TAG_WINDOW_MANAGER = 1L << 5;
+    /** @hide */
     public static final long TRACE_TAG_ACTIVITY_MANAGER = 1L << 6;
+    /** @hide */
     public static final long TRACE_TAG_SYNC_MANAGER = 1L << 7;
+    /** @hide */
     public static final long TRACE_TAG_AUDIO = 1L << 8;
+    /** @hide */
     public static final long TRACE_TAG_VIDEO = 1L << 9;
+    /** @hide */
     public static final long TRACE_TAG_CAMERA = 1L << 10;
+    /** @hide */
     public static final long TRACE_TAG_HAL = 1L << 11;
+    /** @hide */
+    public static final long TRACE_TAG_APP = 1L << 12;
+
     private static final long TRACE_TAG_NOT_READY = 1L << 63;
-
-    public static final int TRACE_FLAGS_START_BIT = 1;
-    public static final String[] TRACE_TAGS = {
-        "Graphics", "Input", "View", "WebView", "Window Manager",
-        "Activity Manager", "Sync Manager", "Audio", "Video", "Camera", "HAL",
-    };
-
-    public static final String PROPERTY_TRACE_TAG_ENABLEFLAGS = "debug.atrace.tags.enableflags";
+    private static final int MAX_SECTION_NAME_LEN = 127;
 
     // Must be volatile to avoid word tearing.
     private static volatile long sEnabledTags = TRACE_TAG_NOT_READY;
@@ -62,6 +76,7 @@
     private static native void nativeTraceCounter(long tag, String name, int value);
     private static native void nativeTraceBegin(long tag, String name);
     private static native void nativeTraceEnd(long tag);
+    private static native void nativeSetAppTracingAllowed(boolean allowed);
 
     static {
         // We configure two separate change callbacks, one in Trace.cpp and one here.  The
@@ -111,6 +126,8 @@
      *
      * @param traceTag The trace tag to check.
      * @return True if the trace tag is valid.
+     *
+     * @hide
      */
     public static boolean isTagEnabled(long traceTag) {
         long tags = sEnabledTags;
@@ -126,6 +143,8 @@
      * @param traceTag The trace tag.
      * @param counterName The counter name to appear in the trace.
      * @param counterValue The counter value.
+     *
+     * @hide
      */
     public static void traceCounter(long traceTag, String counterName, int counterValue) {
         if (isTagEnabled(traceTag)) {
@@ -134,11 +153,28 @@
     }
 
     /**
-     * Writes a trace message to indicate that a given method has begun.
-     * Must be followed by a call to {@link #traceEnd} using the same tag.
+     * Set whether application tracing is allowed for this process.  This is intended to be set
+     * once at application start-up time based on whether the application is debuggable.
+     *
+     * @hide
+     */
+    public static void setAppTracingAllowed(boolean allowed) {
+        nativeSetAppTracingAllowed(allowed);
+
+        // Setting whether app tracing is allowed may change the tags, so we update the cached
+        // tags here.
+        cacheEnabledTags();
+    }
+
+    /**
+     * Writes a trace message to indicate that a given section of code has
+     * begun. Must be followed by a call to {@link #traceEnd} using the same
+     * tag.
      *
      * @param traceTag The trace tag.
      * @param methodName The method name to appear in the trace.
+     *
+     * @hide
      */
     public static void traceBegin(long traceTag, String methodName) {
         if (isTagEnabled(traceTag)) {
@@ -151,10 +187,48 @@
      * Must be called exactly once for each call to {@link #traceBegin} using the same tag.
      *
      * @param traceTag The trace tag.
+     *
+     * @hide
      */
     public static void traceEnd(long traceTag) {
         if (isTagEnabled(traceTag)) {
             nativeTraceEnd(traceTag);
         }
     }
+
+    /**
+     * Writes a trace message to indicate that a given section of code has begun. This call must
+     * be followed by a corresponding call to {@link #traceEnd()} on the same thread.
+     *
+     * <p class="note"> At this time the vertical bar character '|', newline character '\n', and
+     * null character '\0' are used internally by the tracing mechanism.  If sectionName contains
+     * these characters they will be replaced with a space character in the trace.
+     *
+     * @param sectionName The name of the code section to appear in the trace.  This may be at
+     * most 127 Unicode code units long.
+     *
+     * @hide
+     */
+    public static void traceBegin(String sectionName) {
+        if (isTagEnabled(TRACE_TAG_APP)) {
+            if (sectionName.length() > MAX_SECTION_NAME_LEN) {
+                throw new IllegalArgumentException("sectionName is too long");
+            }
+            nativeTraceBegin(TRACE_TAG_APP, sectionName);
+        }
+    }
+
+    /**
+     * Writes a trace message to indicate that a given section of code has ended. This call must
+     * be preceeded by a corresponding call to {@link #traceBegin(String)}. Calling this method
+     * will mark the end of the most recently begun section of code, so care must be taken to
+     * ensure that traceBegin / traceEnd pairs are properly nested and called from the same thread.
+     *
+     * @hide
+     */
+    public static void traceEnd() {
+        if (isTagEnabled(TRACE_TAG_APP)) {
+            nativeTraceEnd(TRACE_TAG_APP);
+        }
+    }
 }
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 367d576..c41c35e 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -35,9 +35,7 @@
 import android.database.DatabaseUtils;
 import android.graphics.Rect;
 import android.net.Uri;
-import android.os.Bundle;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.util.Pair;
@@ -939,6 +937,15 @@
          * its row id changed as a result of a sync or aggregation.
          */
         public static final String LOOKUP_KEY = "lookup";
+
+        /**
+         * Timestamp (milliseconds since epoch) of when this contact was last updated.  This
+         * includes updates to all data associated with this contact including raw contacts.  Any
+         * modification (including deletes and inserts) of underlying contact data are also
+         * reflected in this timestamp.
+         */
+        public static final String CONTACT_LAST_UPDATED_TIMESTAMP =
+                "contact_last_updated_timestamp";
     }
 
     /**
@@ -2113,6 +2120,56 @@
         return id >= Profile.MIN_ID;
     }
 
+    protected interface DeletedContactsColumns {
+
+        /**
+         * A reference to the {@link ContactsContract.Contacts#_ID} that was deleted.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String CONTACT_ID = "contact_id";
+
+        /**
+         * Time (milliseconds since epoch) that the contact was deleted.
+         */
+        public static final String CONTACT_DELETED_TIMESTAMP = "contact_deleted_timestamp";
+    }
+
+    /**
+     * Constants for the deleted contact table.  This table holds a log of deleted contacts.
+     * <p>
+     * Log older than {@link #DAYS_KEPT_MILLISECONDS} may be deleted.
+     */
+    public static final class DeletedContacts implements DeletedContactsColumns {
+
+        /**
+         * This utility class cannot be instantiated
+         */
+        private DeletedContacts() {
+        }
+
+        /**
+         * The content:// style URI for this table, which requests a directory of raw contact rows
+         * matching the selection criteria.
+         */
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI,
+                "deleted_contacts");
+
+        /**
+         * Number of days that the delete log will be kept.  After this time, delete records may be
+         * deleted.
+         *
+         * @hide
+         */
+        private static final int DAYS_KEPT = 30;
+
+        /**
+         * Milliseconds that the delete log will be kept.  After this time, delete records may be
+         * deleted.
+         */
+        public static final long DAYS_KEPT_MILLISECONDS = 1000L * 60L * 60L * 24L * (long)DAYS_KEPT;
+    }
+
+
     protected interface RawContactsColumns {
         /**
          * A reference to the {@link ContactsContract.Contacts#_ID} that this
@@ -7909,6 +7966,13 @@
                 "android.provider.Contacts.SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED";
 
         /**
+         * This is the intent that is fired when the contacts database is created. <p> The
+         * READ_CONTACT permission is required to receive these broadcasts.
+         */
+        public static final String CONTACTS_DATABASE_CREATED =
+                "android.provider.Contacts.DATABASE_CREATED";
+
+        /**
          * Starts an Activity that lets the user pick a contact to attach an image to.
          * After picking the contact it launches the image cropper in face detection mode.
          */
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 8055077..8308459 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -1368,7 +1368,7 @@
                         callbacks.onHardwarePreDraw(canvas);
 
                         if (displayList != null) {
-                            status = drawDisplayList(attachInfo, canvas, displayList, status);
+                            status |= drawDisplayList(attachInfo, canvas, displayList, status);
                         } else {
                             // Shouldn't reach here
                             view.draw(canvas);
diff --git a/core/java/android/view/SimulatedDpad.java b/core/java/android/view/SimulatedDpad.java
deleted file mode 100644
index c889328..0000000
--- a/core/java/android/view/SimulatedDpad.java
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-package android.view;
-
-import android.app.SearchManager;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.util.Log;
-
-/**
- * This class creates DPAD events from TouchNavigation events.
- * 
- * @see ViewRootImpl
- */
-
-//TODO: Make this class an internal class of ViewRootImpl.java
-class SimulatedDpad {
-
-    private static final String TAG = "SimulatedDpad";
-
-    // Maximum difference in milliseconds between the down and up of a touch
-    // event for it to be considered a tap
-    // TODO:Read this value from a configuration file
-    private static final int MAX_TAP_TIME = 250;
-    // Where the cutoff is for determining an edge swipe
-    private static final float EDGE_SWIPE_THRESHOLD = 0.9f;
-    private static final int MSG_FLICK = 313;
-    // TODO: Pass touch slop from the input device
-    private static final int TOUCH_SLOP = 30;
-    // The position of the previous TouchNavigation event
-    private float mLastTouchNavigationXPosition;
-    private float mLastTouchNavigationYPosition;
-    // Where the Touch Navigation was initially pressed
-    private float mTouchNavigationEnterXPosition;
-    private float mTouchNavigationEnterYPosition;
-    // When the most recent ACTION_HOVER_ENTER occurred
-    private long mLastTouchNavigationStartTimeMs = 0;
-    // When the most recent direction key was sent
-    private long mLastTouchNavigationKeySendTimeMs = 0;
-    // When the most recent touch event of any type occurred
-    private long mLastTouchNavigationEventTimeMs = 0;
-    // Did the swipe begin in a valid region
-    private boolean mEdgeSwipePossible;
-
-    private final Context mContext;
-
-    // How quickly keys were sent;
-    private int mKeySendRateMs = 0;
-    private int mLastKeySent;
-    // Last movement in device screen pixels
-    private float mLastMoveX = 0;
-    private float mLastMoveY = 0;
-    // Offset from the initial touch. Gets reset as direction keys are sent.
-    private float mAccumulatedX;
-    private float mAccumulatedY;
-
-    // Change in position allowed during tap events
-    private float mTouchSlop;
-    private float mTouchSlopSquared;
-    // Has the TouchSlop constraint been invalidated
-    private boolean mAlwaysInTapRegion = true;
-
-    // Information from the most recent event.
-    // Used to determine what device sent the event during a fling.
-    private int mLastSource;
-    private int mLastMetaState;
-    private int mLastDeviceId;
-
-    // TODO: Currently using screen dimensions tuned to a Galaxy Nexus, need to
-    // read this from a config file instead
-    private int mDistancePerTick;
-    private int mDistancePerTickSquared;
-    // Highest rate that the flinged events can occur at before dying out
-    private int mMaxRepeatDelay;
-    // The square of the minimum distance needed for a flick to register
-    private int mMinFlickDistanceSquared;
-    // How quickly the repeated events die off
-    private float mFlickDecay;
-
-    public SimulatedDpad(Context context) {
-        mDistancePerTick = SystemProperties.getInt("persist.vr_dist_tick", 64);
-        mDistancePerTickSquared = mDistancePerTick * mDistancePerTick;
-        mMaxRepeatDelay = SystemProperties.getInt("persist.vr_repeat_delay", 300);
-        mMinFlickDistanceSquared = SystemProperties.getInt("persist.vr_min_flick", 20);
-        mMinFlickDistanceSquared *= mMinFlickDistanceSquared;
-        mFlickDecay = Float.parseFloat(SystemProperties.get(
-                "persist.sys.vr_flick_decay", "1.3"));
-        mTouchSlop = TOUCH_SLOP;
-        mTouchSlopSquared = mTouchSlop * mTouchSlop;
-
-        mContext = context;
-    }
-
-    private final Handler mHandler = new Handler(true /*async*/) {
-            @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_FLICK: {
-                    final long time = SystemClock.uptimeMillis();
-                    ViewRootImpl viewroot = (ViewRootImpl) msg.obj;
-                    // Send the key
-                    viewroot.enqueueInputEvent(new KeyEvent(time, time,
-                            KeyEvent.ACTION_DOWN, msg.arg2, 0, mLastMetaState,
-                            mLastDeviceId, 0, KeyEvent.FLAG_FALLBACK, mLastSource));
-                    viewroot.enqueueInputEvent(new KeyEvent(time, time,
-                            KeyEvent.ACTION_UP, msg.arg2, 0, mLastMetaState,
-                            mLastDeviceId, 0, KeyEvent.FLAG_FALLBACK, mLastSource));
-
-                    // Increase the delay by the decay factor and resend
-                    final int delay = (int) Math.ceil(mFlickDecay * msg.arg1);
-                    if (delay <= mMaxRepeatDelay) {
-                        Message msgCopy = Message.obtain(msg);
-                        msgCopy.arg1 = delay;
-                        msgCopy.setAsynchronous(true);
-                        mHandler.sendMessageDelayed(msgCopy, delay);
-                    }
-                    break;
-                }
-            }
-        }
-    };
-
-    public void updateTouchNavigation(ViewRootImpl viewroot, MotionEvent event,
-            boolean synthesizeNewKeys) {
-        if (!synthesizeNewKeys) {
-            mHandler.removeMessages(MSG_FLICK);
-        }
-        InputDevice device = event.getDevice();
-        if (device == null) {
-            return;
-        }
-        // Store what time the TouchNavigation event occurred
-        final long time = SystemClock.uptimeMillis();
-        switch (event.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                mLastTouchNavigationStartTimeMs = time;
-                mAlwaysInTapRegion = true;
-                mTouchNavigationEnterXPosition = event.getX();
-                mTouchNavigationEnterYPosition = event.getY();
-                mAccumulatedX = 0;
-                mAccumulatedY = 0;
-                mLastMoveX = 0;
-                mLastMoveY = 0;
-                if (device.getMotionRange(MotionEvent.AXIS_Y).getMax()
-                        * EDGE_SWIPE_THRESHOLD < event.getY()) {
-                    // Did the swipe begin in a valid region
-                    mEdgeSwipePossible = true;
-                }
-                // Clear any flings
-                if (synthesizeNewKeys) {
-                    mHandler.removeMessages(MSG_FLICK);
-                }
-                break;
-            case MotionEvent.ACTION_MOVE:
-                // Determine whether the move is slop or an intentional move
-                float deltaX = event.getX() - mTouchNavigationEnterXPosition;
-                float deltaY = event.getY() - mTouchNavigationEnterYPosition;
-                if (mTouchSlopSquared < deltaX * deltaX + deltaY * deltaY) {
-                    mAlwaysInTapRegion = false;
-                }
-                // Checks if the swipe has crossed the midpoint
-                // and if our swipe gesture is complete
-                if (event.getY() < (device.getMotionRange(MotionEvent.AXIS_Y).getMax()
-                        * .5) && mEdgeSwipePossible) {
-                    mEdgeSwipePossible = false;
-
-                    Intent intent =
-                            ((SearchManager)mContext.getSystemService(Context.SEARCH_SERVICE))
-                            .getAssistIntent(mContext, false, UserHandle.USER_CURRENT_OR_SELF);
-                    if (intent != null) {
-                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                        try {
-                            mContext.startActivity(intent);
-                        } catch (ActivityNotFoundException e){
-                            Log.e(TAG, "Could not start search activity");
-                        }
-                    } else {
-                        Log.e(TAG, "Could not find a search activity");
-                    }
-                }
-                // Find the difference in position between the two most recent
-                // TouchNavigation events
-                mLastMoveX = event.getX() - mLastTouchNavigationXPosition;
-                mLastMoveY = event.getY() - mLastTouchNavigationYPosition;
-                mAccumulatedX += mLastMoveX;
-                mAccumulatedY += mLastMoveY;
-                float mAccumulatedXSquared = mAccumulatedX * mAccumulatedX;
-                float mAccumulatedYSquared = mAccumulatedY * mAccumulatedY;
-                // Determine if we've moved far enough to send a key press
-                if (mAccumulatedXSquared > mDistancePerTickSquared ||
-                        mAccumulatedYSquared > mDistancePerTickSquared) {
-                    float dominantAxis;
-                    float sign;
-                    boolean isXAxis;
-                    int key;
-                    int repeatCount = 0;
-                    // Determine dominant axis
-                    if (mAccumulatedXSquared > mAccumulatedYSquared) {
-                        dominantAxis = mAccumulatedX;
-                        isXAxis = true;
-                    } else {
-                        dominantAxis = mAccumulatedY;
-                        isXAxis = false;
-                    }
-                    // Determine sign of axis
-                    sign = (dominantAxis > 0) ? 1 : -1;
-                    // Determine key to send
-                    if (isXAxis) {
-                        key = (sign == 1) ? KeyEvent.KEYCODE_DPAD_RIGHT :
-                                KeyEvent.KEYCODE_DPAD_LEFT;
-                    } else {
-                        key = (sign == 1) ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP;
-                    }
-                    // Send key until maximum distance constraint is satisfied
-                    while (dominantAxis * dominantAxis > mDistancePerTickSquared) {
-                        repeatCount++;
-                        dominantAxis -= sign * mDistancePerTick;
-                        if (synthesizeNewKeys) {
-                            viewroot.enqueueInputEvent(new KeyEvent(time, time,
-                                    KeyEvent.ACTION_DOWN, key, 0, event.getMetaState(),
-                                    event.getDeviceId(), 0, KeyEvent.FLAG_FALLBACK,
-                                    event.getSource()));
-                            viewroot.enqueueInputEvent(new KeyEvent(time, time,
-                                    KeyEvent.ACTION_UP, key, 0, event.getMetaState(),
-                                    event.getDeviceId(), 0, KeyEvent.FLAG_FALLBACK,
-                                    event.getSource()));
-                        }
-                    }
-                    // Save new axis values
-                    mAccumulatedX = isXAxis ? dominantAxis : 0;
-                    mAccumulatedY = isXAxis ? 0 : dominantAxis;
-
-                    mLastKeySent = key;
-                    mKeySendRateMs = (int) (time - mLastTouchNavigationKeySendTimeMs) / repeatCount;
-                    mLastTouchNavigationKeySendTimeMs = time;
-                }
-                break;
-            case MotionEvent.ACTION_UP:
-                if (time - mLastTouchNavigationStartTimeMs < MAX_TAP_TIME && mAlwaysInTapRegion) {
-                    if (synthesizeNewKeys) {
-                        viewroot.enqueueInputEvent(new KeyEvent(mLastTouchNavigationStartTimeMs,
-                                    time, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 0,
-                                    event.getMetaState(), event.getDeviceId(), 0,
-                                    KeyEvent.FLAG_FALLBACK, event.getSource()));
-                        viewroot.enqueueInputEvent(new KeyEvent(mLastTouchNavigationStartTimeMs,
-                                    time, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, 0,
-                                    event.getMetaState(), event.getDeviceId(), 0,
-                                    KeyEvent.FLAG_FALLBACK, event.getSource()));
-                    }
-                } else {
-                    float xMoveSquared = mLastMoveX * mLastMoveX;
-                    float yMoveSquared = mLastMoveY * mLastMoveY;
-                    // Determine whether the last gesture was a fling.
-                    if (mMinFlickDistanceSquared <= xMoveSquared + yMoveSquared &&
-                            time - mLastTouchNavigationEventTimeMs <= MAX_TAP_TIME &&
-                            mKeySendRateMs <= mMaxRepeatDelay && mKeySendRateMs > 0) {
-                        mLastDeviceId = event.getDeviceId();
-                        mLastSource = event.getSource();
-                        mLastMetaState = event.getMetaState();
-
-                        if (synthesizeNewKeys) {
-                            Message message = Message.obtain(mHandler, MSG_FLICK,
-                                    mKeySendRateMs, mLastKeySent, viewroot);
-                            message.setAsynchronous(true);
-                            mHandler.sendMessageDelayed(message, mKeySendRateMs);
-                        }
-                    }
-                }
-                mEdgeSwipePossible = false;
-                break;
-        }
-
-        // Store touch event position and time
-        mLastTouchNavigationEventTimeMs = time;
-        mLastTouchNavigationXPosition = event.getX();
-        mLastTouchNavigationYPosition = event.getY();
-    }
-}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 22586f6..98b7877 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2923,8 +2923,6 @@
     private final static int MSG_DISPATCH_DONE_ANIMATING = 22;
     private final static int MSG_INVALIDATE_WORLD = 23;
     private final static int MSG_WINDOW_MOVED = 24;
-    private final static int MSG_ENQUEUE_X_AXIS_KEY_REPEAT = 25;
-    private final static int MSG_ENQUEUE_Y_AXIS_KEY_REPEAT = 26;
 
     final class ViewRootHandler extends Handler {
         @Override
@@ -2974,10 +2972,6 @@
                     return "MSG_DISPATCH_DONE_ANIMATING";
                 case MSG_WINDOW_MOVED:
                     return "MSG_WINDOW_MOVED";
-                case MSG_ENQUEUE_X_AXIS_KEY_REPEAT:
-                    return "MSG_ENQUEUE_X_AXIS_KEY_REPEAT";
-                case MSG_ENQUEUE_Y_AXIS_KEY_REPEAT:
-                    return "MSG_ENQUEUE_Y_AXIS_KEY_REPEAT";
             }
             return super.getMessageName(message);
         }
@@ -3200,18 +3194,6 @@
                     invalidateWorld(mView);
                 }
             } break;
-            case MSG_ENQUEUE_X_AXIS_KEY_REPEAT:
-            case MSG_ENQUEUE_Y_AXIS_KEY_REPEAT: {
-                KeyEvent oldEvent = (KeyEvent)msg.obj;
-                KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent, SystemClock.uptimeMillis(),
-                        oldEvent.getRepeatCount() + 1);
-                if (mAttachInfo.mHasWindowFocus) {
-                    enqueueInputEvent(e);
-                    Message m = obtainMessage(msg.what, e);
-                    m.setAsynchronous(true);
-                    sendMessageDelayed(m, mViewConfiguration.getKeyRepeatDelay());
-                }
-            } break;
             }
         }
     }
@@ -3877,37 +3859,34 @@
     }
 
     /**
-     * Performs default processing of unhandled input events.
+     * Performs synthesis of new input events from unhandled input events.
      */
     final class SyntheticInputStage extends InputStage {
-        private final TrackballAxis mTrackballAxisX = new TrackballAxis();
-        private final TrackballAxis mTrackballAxisY = new TrackballAxis();
-        private long mLastTrackballTime;
-
-        private int mLastJoystickXDirection;
-        private int mLastJoystickYDirection;
-        private int mLastJoystickXKeyCode;
-        private int mLastJoystickYKeyCode;
-
-        private SimulatedDpad mSimulatedDpad;
+        private final SyntheticTrackballHandler mTrackball = new SyntheticTrackballHandler();
+        private final SyntheticJoystickHandler mJoystick = new SyntheticJoystickHandler();
+        private final SyntheticTouchNavigationHandler mTouchNavigation =
+                new SyntheticTouchNavigationHandler();
 
         public SyntheticInputStage() {
             super(null);
-            mSimulatedDpad = new SimulatedDpad(mContext);
         }
 
         @Override
         protected int onProcess(QueuedInputEvent q) {
             q.mFlags |= QueuedInputEvent.FLAG_RESYNTHESIZED;
             if (q.mEvent instanceof MotionEvent) {
-                final int source = q.mEvent.getSource();
+                final MotionEvent event = (MotionEvent)q.mEvent;
+                final int source = event.getSource();
                 if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
-                    return processTrackballEvent(q);
+                    mTrackball.process(event);
+                    return FINISH_HANDLED;
                 } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
-                    return processJoystickEvent(q);
+                    mJoystick.process(event);
+                    return FINISH_HANDLED;
                 } else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION)
                         == InputDevice.SOURCE_TOUCH_NAVIGATION) {
-                    return processTouchNavigationEvent(q);
+                    mTouchNavigation.process(event);
+                    return FINISH_HANDLED;
                 }
             }
             return FORWARD;
@@ -3918,49 +3897,55 @@
             if ((q.mFlags & QueuedInputEvent.FLAG_RESYNTHESIZED) == 0) {
                 // Cancel related synthetic events if any prior stage has handled the event.
                 if (q.mEvent instanceof MotionEvent) {
-                    final int source = q.mEvent.getSource();
+                    final MotionEvent event = (MotionEvent)q.mEvent;
+                    final int source = event.getSource();
                     if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
-                        cancelTrackballEvent(q);
+                        mTrackball.cancel(event);
                     } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
-                        cancelJoystickEvent(q);
+                        mJoystick.cancel(event);
                     } else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION)
                             == InputDevice.SOURCE_TOUCH_NAVIGATION) {
-                        cancelTouchNavigationEvent(q);
+                        mTouchNavigation.cancel(event);
                     }
                 }
             }
             super.onDeliverToNext(q);
         }
+    }
 
-        private int processTrackballEvent(QueuedInputEvent q) {
-            final MotionEvent event = (MotionEvent)q.mEvent;
+    /**
+     * Creates dpad events from unhandled trackball movements.
+     */
+    final class SyntheticTrackballHandler {
+        private final TrackballAxis mX = new TrackballAxis();
+        private final TrackballAxis mY = new TrackballAxis();
+        private long mLastTime;
 
+        public void process(MotionEvent event) {
             // Translate the trackball event into DPAD keys and try to deliver those.
-            final TrackballAxis x = mTrackballAxisX;
-            final TrackballAxis y = mTrackballAxisY;
             long curTime = SystemClock.uptimeMillis();
-            if ((mLastTrackballTime + MAX_TRACKBALL_DELAY) < curTime) {
+            if ((mLastTime + MAX_TRACKBALL_DELAY) < curTime) {
                 // It has been too long since the last movement,
                 // so restart at the beginning.
-                x.reset(0);
-                y.reset(0);
-                mLastTrackballTime = curTime;
+                mX.reset(0);
+                mY.reset(0);
+                mLastTime = curTime;
             }
 
             final int action = event.getAction();
             final int metaState = event.getMetaState();
             switch (action) {
                 case MotionEvent.ACTION_DOWN:
-                    x.reset(2);
-                    y.reset(2);
+                    mX.reset(2);
+                    mY.reset(2);
                     enqueueInputEvent(new KeyEvent(curTime, curTime,
                             KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState,
                             KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
                             InputDevice.SOURCE_KEYBOARD));
                     break;
                 case MotionEvent.ACTION_UP:
-                    x.reset(2);
-                    y.reset(2);
+                    mX.reset(2);
+                    mY.reset(2);
                     enqueueInputEvent(new KeyEvent(curTime, curTime,
                             KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState,
                             KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
@@ -3968,14 +3953,14 @@
                     break;
             }
 
-            if (DEBUG_TRACKBALL) Log.v(TAG, "TB X=" + x.position + " step="
-                    + x.step + " dir=" + x.dir + " acc=" + x.acceleration
+            if (DEBUG_TRACKBALL) Log.v(TAG, "TB X=" + mX.position + " step="
+                    + mX.step + " dir=" + mX.dir + " acc=" + mX.acceleration
                     + " move=" + event.getX()
-                    + " / Y=" + y.position + " step="
-                    + y.step + " dir=" + y.dir + " acc=" + y.acceleration
+                    + " / Y=" + mY.position + " step="
+                    + mY.step + " dir=" + mY.dir + " acc=" + mY.acceleration
                     + " move=" + event.getY());
-            final float xOff = x.collect(event.getX(), event.getEventTime(), "X");
-            final float yOff = y.collect(event.getY(), event.getEventTime(), "Y");
+            final float xOff = mX.collect(event.getX(), event.getEventTime(), "X");
+            final float yOff = mY.collect(event.getY(), event.getEventTime(), "Y");
 
             // Generate DPAD events based on the trackball movement.
             // We pick the axis that has moved the most as the direction of
@@ -3987,20 +3972,20 @@
             int movement = 0;
             float accel = 1;
             if (xOff > yOff) {
-                movement = x.generate();
+                movement = mX.generate();
                 if (movement != 0) {
                     keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT
                             : KeyEvent.KEYCODE_DPAD_LEFT;
-                    accel = x.acceleration;
-                    y.reset(2);
+                    accel = mX.acceleration;
+                    mY.reset(2);
                 }
             } else if (yOff > 0) {
-                movement = y.generate();
+                movement = mY.generate();
                 if (movement != 0) {
                     keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_DOWN
                             : KeyEvent.KEYCODE_DPAD_UP;
-                    accel = y.acceleration;
-                    x.reset(2);
+                    accel = mY.acceleration;
+                    mX.reset(2);
                 }
             }
 
@@ -4034,16 +4019,12 @@
                             KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
                             InputDevice.SOURCE_KEYBOARD));
                 }
-                mLastTrackballTime = curTime;
+                mLastTime = curTime;
             }
-
-            // Unfortunately we can't tell whether the application consumed the keys, so
-            // we always consider the trackball event handled.
-            return FINISH_HANDLED;
         }
 
-        private void cancelTrackballEvent(QueuedInputEvent q) {
-            mLastTrackballTime = Integer.MIN_VALUE;
+        public void cancel(MotionEvent event) {
+            mLastTime = Integer.MIN_VALUE;
 
             // If we reach this, we consumed a trackball event.
             // Because we will not translate the trackball event into a key event,
@@ -4052,19 +4033,220 @@
                 ensureTouchMode(false);
             }
         }
+    }
 
-        private int processJoystickEvent(QueuedInputEvent q) {
-            final MotionEvent event = (MotionEvent)q.mEvent;
-            updateJoystickDirection(event, true);
-            return FINISH_HANDLED;
+    /**
+     * Maintains state information for a single trackball axis, generating
+     * discrete (DPAD) movements based on raw trackball motion.
+     */
+    static final class TrackballAxis {
+        /**
+         * The maximum amount of acceleration we will apply.
+         */
+        static final float MAX_ACCELERATION = 20;
+
+        /**
+         * The maximum amount of time (in milliseconds) between events in order
+         * for us to consider the user to be doing fast trackball movements,
+         * and thus apply an acceleration.
+         */
+        static final long FAST_MOVE_TIME = 150;
+
+        /**
+         * Scaling factor to the time (in milliseconds) between events to how
+         * much to multiple/divide the current acceleration.  When movement
+         * is < FAST_MOVE_TIME this multiplies the acceleration; when >
+         * FAST_MOVE_TIME it divides it.
+         */
+        static final float ACCEL_MOVE_SCALING_FACTOR = (1.0f/40);
+
+        static final float FIRST_MOVEMENT_THRESHOLD = 0.5f;
+        static final float SECOND_CUMULATIVE_MOVEMENT_THRESHOLD = 2.0f;
+        static final float SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD = 1.0f;
+
+        float position;
+        float acceleration = 1;
+        long lastMoveTime = 0;
+        int step;
+        int dir;
+        int nonAccelMovement;
+
+        void reset(int _step) {
+            position = 0;
+            acceleration = 1;
+            lastMoveTime = 0;
+            step = _step;
+            dir = 0;
         }
 
-        private void cancelJoystickEvent(QueuedInputEvent q) {
-            final MotionEvent event = (MotionEvent)q.mEvent;
-            updateJoystickDirection(event, false);
+        /**
+         * Add trackball movement into the state.  If the direction of movement
+         * has been reversed, the state is reset before adding the
+         * movement (so that you don't have to compensate for any previously
+         * collected movement before see the result of the movement in the
+         * new direction).
+         *
+         * @return Returns the absolute value of the amount of movement
+         * collected so far.
+         */
+        float collect(float off, long time, String axis) {
+            long normTime;
+            if (off > 0) {
+                normTime = (long)(off * FAST_MOVE_TIME);
+                if (dir < 0) {
+                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to positive!");
+                    position = 0;
+                    step = 0;
+                    acceleration = 1;
+                    lastMoveTime = 0;
+                }
+                dir = 1;
+            } else if (off < 0) {
+                normTime = (long)((-off) * FAST_MOVE_TIME);
+                if (dir > 0) {
+                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to negative!");
+                    position = 0;
+                    step = 0;
+                    acceleration = 1;
+                    lastMoveTime = 0;
+                }
+                dir = -1;
+            } else {
+                normTime = 0;
+            }
+
+            // The number of milliseconds between each movement that is
+            // considered "normal" and will not result in any acceleration
+            // or deceleration, scaled by the offset we have here.
+            if (normTime > 0) {
+                long delta = time - lastMoveTime;
+                lastMoveTime = time;
+                float acc = acceleration;
+                if (delta < normTime) {
+                    // The user is scrolling rapidly, so increase acceleration.
+                    float scale = (normTime-delta) * ACCEL_MOVE_SCALING_FACTOR;
+                    if (scale > 1) acc *= scale;
+                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " accelerate: off="
+                            + off + " normTime=" + normTime + " delta=" + delta
+                            + " scale=" + scale + " acc=" + acc);
+                    acceleration = acc < MAX_ACCELERATION ? acc : MAX_ACCELERATION;
+                } else {
+                    // The user is scrolling slowly, so decrease acceleration.
+                    float scale = (delta-normTime) * ACCEL_MOVE_SCALING_FACTOR;
+                    if (scale > 1) acc /= scale;
+                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " deccelerate: off="
+                            + off + " normTime=" + normTime + " delta=" + delta
+                            + " scale=" + scale + " acc=" + acc);
+                    acceleration = acc > 1 ? acc : 1;
+                }
+            }
+            position += off;
+            return Math.abs(position);
         }
 
-        private void updateJoystickDirection(MotionEvent event, boolean synthesizeNewKeys) {
+        /**
+         * Generate the number of discrete movement events appropriate for
+         * the currently collected trackball movement.
+         *
+         * @return Returns the number of discrete movements, either positive
+         * or negative, or 0 if there is not enough trackball movement yet
+         * for a discrete movement.
+         */
+        int generate() {
+            int movement = 0;
+            nonAccelMovement = 0;
+            do {
+                final int dir = position >= 0 ? 1 : -1;
+                switch (step) {
+                    // If we are going to execute the first step, then we want
+                    // to do this as soon as possible instead of waiting for
+                    // a full movement, in order to make things look responsive.
+                    case 0:
+                        if (Math.abs(position) < FIRST_MOVEMENT_THRESHOLD) {
+                            return movement;
+                        }
+                        movement += dir;
+                        nonAccelMovement += dir;
+                        step = 1;
+                        break;
+                    // If we have generated the first movement, then we need
+                    // to wait for the second complete trackball motion before
+                    // generating the second discrete movement.
+                    case 1:
+                        if (Math.abs(position) < SECOND_CUMULATIVE_MOVEMENT_THRESHOLD) {
+                            return movement;
+                        }
+                        movement += dir;
+                        nonAccelMovement += dir;
+                        position -= SECOND_CUMULATIVE_MOVEMENT_THRESHOLD * dir;
+                        step = 2;
+                        break;
+                    // After the first two, we generate discrete movements
+                    // consistently with the trackball, applying an acceleration
+                    // if the trackball is moving quickly.  This is a simple
+                    // acceleration on top of what we already compute based
+                    // on how quickly the wheel is being turned, to apply
+                    // a longer increasing acceleration to continuous movement
+                    // in one direction.
+                    default:
+                        if (Math.abs(position) < SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD) {
+                            return movement;
+                        }
+                        movement += dir;
+                        position -= dir * SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD;
+                        float acc = acceleration;
+                        acc *= 1.1f;
+                        acceleration = acc < MAX_ACCELERATION ? acc : acceleration;
+                        break;
+                }
+            } while (true);
+        }
+    }
+
+    /**
+     * Creates dpad events from unhandled joystick movements.
+     */
+    final class SyntheticJoystickHandler extends Handler {
+        private final static int MSG_ENQUEUE_X_AXIS_KEY_REPEAT = 1;
+        private final static int MSG_ENQUEUE_Y_AXIS_KEY_REPEAT = 2;
+
+        private int mLastXDirection;
+        private int mLastYDirection;
+        private int mLastXKeyCode;
+        private int mLastYKeyCode;
+
+        public SyntheticJoystickHandler() {
+            super(true);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_ENQUEUE_X_AXIS_KEY_REPEAT:
+                case MSG_ENQUEUE_Y_AXIS_KEY_REPEAT: {
+                    KeyEvent oldEvent = (KeyEvent)msg.obj;
+                    KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent,
+                            SystemClock.uptimeMillis(),
+                            oldEvent.getRepeatCount() + 1);
+                    if (mAttachInfo.mHasWindowFocus) {
+                        enqueueInputEvent(e);
+                        Message m = obtainMessage(msg.what, e);
+                        m.setAsynchronous(true);
+                        sendMessageDelayed(m, ViewConfiguration.getKeyRepeatDelay());
+                    }
+                } break;
+            }
+        }
+
+        public void process(MotionEvent event) {
+            update(event, true);
+        }
+
+        public void cancel(MotionEvent event) {
+            update(event, false);
+        }
+
+        private void update(MotionEvent event, boolean synthesizeNewKeys) {
             final long time = event.getEventTime();
             final int metaState = event.getMetaState();
             final int deviceId = event.getDeviceId();
@@ -4082,51 +4264,51 @@
                 yDirection = joystickAxisValueToDirection(event.getY());
             }
 
-            if (xDirection != mLastJoystickXDirection) {
-                if (mLastJoystickXKeyCode != 0) {
-                    mHandler.removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);
+            if (xDirection != mLastXDirection) {
+                if (mLastXKeyCode != 0) {
+                    removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);
                     enqueueInputEvent(new KeyEvent(time, time,
-                            KeyEvent.ACTION_UP, mLastJoystickXKeyCode, 0, metaState,
+                            KeyEvent.ACTION_UP, mLastXKeyCode, 0, metaState,
                             deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
-                    mLastJoystickXKeyCode = 0;
+                    mLastXKeyCode = 0;
                 }
 
-                mLastJoystickXDirection = xDirection;
+                mLastXDirection = xDirection;
 
                 if (xDirection != 0 && synthesizeNewKeys) {
-                    mLastJoystickXKeyCode = xDirection > 0
+                    mLastXKeyCode = xDirection > 0
                             ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT;
                     final KeyEvent e = new KeyEvent(time, time,
-                            KeyEvent.ACTION_DOWN, mLastJoystickXKeyCode, 0, metaState,
+                            KeyEvent.ACTION_DOWN, mLastXKeyCode, 0, metaState,
                             deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
                     enqueueInputEvent(e);
-                    Message m = mHandler.obtainMessage(MSG_ENQUEUE_X_AXIS_KEY_REPEAT, e);
+                    Message m = obtainMessage(MSG_ENQUEUE_X_AXIS_KEY_REPEAT, e);
                     m.setAsynchronous(true);
                     mHandler.sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
                 }
             }
 
-            if (yDirection != mLastJoystickYDirection) {
-                if (mLastJoystickYKeyCode != 0) {
-                    mHandler.removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);
+            if (yDirection != mLastYDirection) {
+                if (mLastYKeyCode != 0) {
+                    removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);
                     enqueueInputEvent(new KeyEvent(time, time,
-                            KeyEvent.ACTION_UP, mLastJoystickYKeyCode, 0, metaState,
+                            KeyEvent.ACTION_UP, mLastYKeyCode, 0, metaState,
                             deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
-                    mLastJoystickYKeyCode = 0;
+                    mLastYKeyCode = 0;
                 }
 
-                mLastJoystickYDirection = yDirection;
+                mLastYDirection = yDirection;
 
                 if (yDirection != 0 && synthesizeNewKeys) {
-                    mLastJoystickYKeyCode = yDirection > 0
+                    mLastYKeyCode = yDirection > 0
                             ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP;
                     final KeyEvent e = new KeyEvent(time, time,
-                            KeyEvent.ACTION_DOWN, mLastJoystickYKeyCode, 0, metaState,
+                            KeyEvent.ACTION_DOWN, mLastYKeyCode, 0, metaState,
                             deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
                     enqueueInputEvent(e);
-                    Message m = mHandler.obtainMessage(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT, e);
+                    Message m = obtainMessage(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT, e);
                     m.setAsynchronous(true);
-                    mHandler.sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
+                    sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
                 }
             }
         }
@@ -4140,17 +4322,419 @@
                 return 0;
             }
         }
+    }
 
-        private int processTouchNavigationEvent(QueuedInputEvent q) {
-            final MotionEvent event = (MotionEvent)q.mEvent;
-            mSimulatedDpad.updateTouchNavigation(ViewRootImpl.this, event, true);
-            return FINISH_HANDLED;
+    /**
+     * Creates dpad events from unhandled touch navigation movements.
+     */
+    final class SyntheticTouchNavigationHandler extends Handler {
+        private static final String LOCAL_TAG = "SyntheticTouchNavigationHandler";
+        private static final boolean LOCAL_DEBUG = false;
+
+        // Assumed nominal width and height in millimeters of a touch navigation pad,
+        // if no resolution information is available from the input system.
+        private static final float DEFAULT_WIDTH_MILLIMETERS = 48;
+        private static final float DEFAULT_HEIGHT_MILLIMETERS = 48;
+
+        /* TODO: These constants should eventually be moved to ViewConfiguration. */
+
+        // Tap timeout in milliseconds.
+        private static final int TAP_TIMEOUT = 250;
+
+        // The maximum distance traveled for a gesture to be considered a tap in millimeters.
+        private static final int TAP_SLOP_MILLIMETERS = 5;
+
+        // The nominal distance traveled to move by one unit.
+        private static final int TICK_DISTANCE_MILLIMETERS = 12;
+
+        // Minimum and maximum fling velocity in ticks per second.
+        // The minimum velocity should be set such that we perform enough ticks per
+        // second that the fling appears to be fluid.  For example, if we set the minimum
+        // to 2 ticks per second, then there may be up to half a second delay between the next
+        // to last and last ticks which is noticeably discrete and jerky.  This value should
+        // probably not be set to anything less than about 4.
+        // If fling accuracy is a problem then consider tuning the tick distance instead.
+        private static final float MIN_FLING_VELOCITY_TICKS_PER_SECOND = 6f;
+        private static final float MAX_FLING_VELOCITY_TICKS_PER_SECOND = 20f;
+
+        // Fling velocity decay factor applied after each new key is emitted.
+        // This parameter controls the deceleration and overall duration of the fling.
+        // The fling stops automatically when its velocity drops below the minimum
+        // fling velocity defined above.
+        private static final float FLING_TICK_DECAY = 0.8f;
+
+        /* The input device that we are tracking. */
+
+        private int mCurrentDeviceId = -1;
+        private int mCurrentSource;
+        private boolean mCurrentDeviceSupported;
+
+        /* Configuration for the current input device. */
+
+        // The tap timeout and scaled slop.
+        private int mConfigTapTimeout;
+        private float mConfigTapSlop;
+
+        // The scaled tick distance.  A movement of this amount should generally translate
+        // into a single dpad event in a given direction.
+        private float mConfigTickDistance;
+
+        // The minimum and maximum scaled fling velocity.
+        private float mConfigMinFlingVelocity;
+        private float mConfigMaxFlingVelocity;
+
+        /* Tracking state. */
+
+        // The velocity tracker for detecting flings.
+        private VelocityTracker mVelocityTracker;
+
+        // The active pointer id, or -1 if none.
+        private int mActivePointerId = -1;
+
+        // Time and location where tracking started.
+        private long mStartTime;
+        private float mStartX;
+        private float mStartY;
+
+        // Most recently observed position.
+        private float mLastX;
+        private float mLastY;
+
+        // Accumulated movement delta since the last direction key was sent.
+        private float mAccumulatedX;
+        private float mAccumulatedY;
+
+        // Set to true if any movement was delivered to the app.
+        // Implies that tap slop was exceeded.
+        private boolean mConsumedMovement;
+
+        // The most recently sent key down event.
+        // The keycode remains set until the direction changes or a fling ends
+        // so that repeated key events may be generated as required.
+        private long mPendingKeyDownTime;
+        private int mPendingKeyCode = KeyEvent.KEYCODE_UNKNOWN;
+        private int mPendingKeyRepeatCount;
+        private int mPendingKeyMetaState;
+
+        // The current fling velocity while a fling is in progress.
+        private boolean mFlinging;
+        private float mFlingVelocity;
+
+        public SyntheticTouchNavigationHandler() {
+            super(true);
         }
 
-        private void cancelTouchNavigationEvent(QueuedInputEvent q) {
-            final MotionEvent event = (MotionEvent)q.mEvent;
-            mSimulatedDpad.updateTouchNavigation(ViewRootImpl.this, event, false);
+        public void process(MotionEvent event) {
+            // Update the current device information.
+            final long time = event.getEventTime();
+            final int deviceId = event.getDeviceId();
+            final int source = event.getSource();
+            if (mCurrentDeviceId != deviceId || mCurrentSource != source) {
+                finishKeys(time);
+                finishTracking(time);
+                mCurrentDeviceId = deviceId;
+                mCurrentSource = source;
+                mCurrentDeviceSupported = false;
+                InputDevice device = event.getDevice();
+                if (device != null) {
+                    // In order to support an input device, we must know certain
+                    // characteristics about it, such as its size and resolution.
+                    InputDevice.MotionRange xRange = device.getMotionRange(MotionEvent.AXIS_X);
+                    InputDevice.MotionRange yRange = device.getMotionRange(MotionEvent.AXIS_Y);
+                    if (xRange != null && yRange != null) {
+                        mCurrentDeviceSupported = true;
+
+                        // Infer the resolution if it not actually known.
+                        float xRes = xRange.getResolution();
+                        if (xRes <= 0) {
+                            xRes = xRange.getRange() / DEFAULT_WIDTH_MILLIMETERS;
+                        }
+                        float yRes = yRange.getResolution();
+                        if (yRes <= 0) {
+                            yRes = yRange.getRange() / DEFAULT_HEIGHT_MILLIMETERS;
+                        }
+                        float nominalRes = (xRes + yRes) * 0.5f;
+
+                        // Precompute all of the configuration thresholds we will need.
+                        mConfigTapTimeout = TAP_TIMEOUT;
+                        mConfigTapSlop = TAP_SLOP_MILLIMETERS * nominalRes;
+                        mConfigTickDistance = TICK_DISTANCE_MILLIMETERS * nominalRes;
+                        mConfigMinFlingVelocity =
+                                MIN_FLING_VELOCITY_TICKS_PER_SECOND * mConfigTickDistance;
+                        mConfigMaxFlingVelocity =
+                                MAX_FLING_VELOCITY_TICKS_PER_SECOND * mConfigTickDistance;
+
+                        if (LOCAL_DEBUG) {
+                            Log.d(LOCAL_TAG, "Configured device " + mCurrentDeviceId
+                                    + " (" + Integer.toHexString(mCurrentSource) + "): "
+                                    + "mConfigTapTimeout=" + mConfigTapTimeout
+                                    + ", mConfigTapSlop=" + mConfigTapSlop
+                                    + ", mConfigTickDistance=" + mConfigTickDistance
+                                    + ", mConfigMinFlingVelocity=" + mConfigMinFlingVelocity
+                                    + ", mConfigMaxFlingVelocity=" + mConfigMaxFlingVelocity);
+                        }
+                    }
+                }
+            }
+            if (!mCurrentDeviceSupported) {
+                return;
+            }
+
+            // Handle the event.
+            final int action = event.getActionMasked();
+            switch (action) {
+                case MotionEvent.ACTION_DOWN: {
+                    boolean caughtFling = mFlinging;
+                    finishKeys(time);
+                    finishTracking(time);
+                    mActivePointerId = event.getPointerId(0);
+                    mVelocityTracker = VelocityTracker.obtain();
+                    mVelocityTracker.addMovement(event);
+                    mStartTime = time;
+                    mStartX = event.getX();
+                    mStartY = event.getY();
+                    mLastX = mStartX;
+                    mLastY = mStartY;
+                    mAccumulatedX = 0;
+                    mAccumulatedY = 0;
+
+                    // If we caught a fling, then pretend that the tap slop has already
+                    // been exceeded to suppress taps whose only purpose is to stop the fling.
+                    mConsumedMovement = caughtFling;
+                    break;
+                }
+
+                case MotionEvent.ACTION_MOVE:
+                case MotionEvent.ACTION_UP: {
+                    if (mActivePointerId < 0) {
+                        break;
+                    }
+                    final int index = event.findPointerIndex(mActivePointerId);
+                    if (index < 0) {
+                        finishKeys(time);
+                        finishTracking(time);
+                        break;
+                    }
+
+                    mVelocityTracker.addMovement(event);
+                    final float x = event.getX(index);
+                    final float y = event.getY(index);
+                    mAccumulatedX += x - mLastX;
+                    mAccumulatedY += y - mLastY;
+                    mLastX = x;
+                    mLastY = y;
+
+                    // Consume any accumulated movement so far.
+                    final int metaState = event.getMetaState();
+                    consumeAccumulatedMovement(time, metaState);
+
+                    // Detect taps and flings.
+                    if (action == MotionEvent.ACTION_UP) {
+                        if (!mConsumedMovement
+                                && Math.hypot(mLastX - mStartX, mLastY - mStartY) < mConfigTapSlop
+                                && time <= mStartTime + mConfigTapTimeout) {
+                            // It's a tap!
+                            finishKeys(time);
+                            sendKeyDownOrRepeat(time, KeyEvent.KEYCODE_DPAD_CENTER, metaState);
+                            sendKeyUp(time);
+                        } else if (mConsumedMovement
+                                && mPendingKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
+                            // It might be a fling.
+                            mVelocityTracker.computeCurrentVelocity(1000, mConfigMaxFlingVelocity);
+                            final float vx = mVelocityTracker.getXVelocity(mActivePointerId);
+                            final float vy = mVelocityTracker.getYVelocity(mActivePointerId);
+                            if (!startFling(time, vx, vy)) {
+                                finishKeys(time);
+                            }
+                        }
+                        finishTracking(time);
+                    }
+                    break;
+                }
+
+                case MotionEvent.ACTION_CANCEL: {
+                    finishKeys(time);
+                    finishTracking(time);
+                    break;
+                }
+            }
         }
+
+        public void cancel(MotionEvent event) {
+            if (mCurrentDeviceId == event.getDeviceId()
+                    && mCurrentSource == event.getSource()) {
+                final long time = event.getEventTime();
+                finishKeys(time);
+                finishTracking(time);
+            }
+        }
+
+        private void finishKeys(long time) {
+            cancelFling();
+            sendKeyUp(time);
+        }
+
+        private void finishTracking(long time) {
+            if (mActivePointerId >= 0) {
+                mActivePointerId = -1;
+                mVelocityTracker.recycle();
+                mVelocityTracker = null;
+            }
+        }
+
+        private void consumeAccumulatedMovement(long time, int metaState) {
+            final float absX = Math.abs(mAccumulatedX);
+            final float absY = Math.abs(mAccumulatedY);
+            if (absX >= absY) {
+                if (absX >= mConfigTickDistance) {
+                    mAccumulatedX = consumeAccumulatedMovement(time, metaState, mAccumulatedX,
+                            KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT);
+                    mAccumulatedY = 0;
+                    mConsumedMovement = true;
+                }
+            } else {
+                if (absY >= mConfigTickDistance) {
+                    mAccumulatedY = consumeAccumulatedMovement(time, metaState, mAccumulatedY,
+                            KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN);
+                    mAccumulatedX = 0;
+                    mConsumedMovement = true;
+                }
+            }
+        }
+
+        private float consumeAccumulatedMovement(long time, int metaState,
+                float accumulator, int negativeKeyCode, int positiveKeyCode) {
+            while (accumulator <= -mConfigTickDistance) {
+                sendKeyDownOrRepeat(time, negativeKeyCode, metaState);
+                accumulator += mConfigTickDistance;
+            }
+            while (accumulator >= mConfigTickDistance) {
+                sendKeyDownOrRepeat(time, positiveKeyCode, metaState);
+                accumulator -= mConfigTickDistance;
+            }
+            return accumulator;
+        }
+
+        private void sendKeyDownOrRepeat(long time, int keyCode, int metaState) {
+            if (mPendingKeyCode != keyCode) {
+                sendKeyUp(time);
+                mPendingKeyDownTime = time;
+                mPendingKeyCode = keyCode;
+                mPendingKeyRepeatCount = 0;
+            } else {
+                mPendingKeyRepeatCount += 1;
+            }
+            mPendingKeyMetaState = metaState;
+
+            // Note: Normally we would pass FLAG_LONG_PRESS when the repeat count is 1
+            // but it doesn't quite make sense when simulating the events in this way.
+            if (LOCAL_DEBUG) {
+                Log.d(LOCAL_TAG, "Sending key down: keyCode=" + mPendingKeyCode
+                        + ", repeatCount=" + mPendingKeyRepeatCount
+                        + ", metaState=" + Integer.toHexString(mPendingKeyMetaState));
+            }
+            enqueueInputEvent(new KeyEvent(mPendingKeyDownTime, time,
+                    KeyEvent.ACTION_DOWN, mPendingKeyCode, mPendingKeyRepeatCount,
+                    mPendingKeyMetaState, mCurrentDeviceId,
+                    KeyEvent.FLAG_FALLBACK, mCurrentSource));
+        }
+
+        private void sendKeyUp(long time) {
+            if (mPendingKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
+                if (LOCAL_DEBUG) {
+                    Log.d(LOCAL_TAG, "Sending key up: keyCode=" + mPendingKeyCode
+                            + ", metaState=" + Integer.toHexString(mPendingKeyMetaState));
+                }
+                enqueueInputEvent(new KeyEvent(mPendingKeyDownTime, time,
+                        KeyEvent.ACTION_UP, mPendingKeyCode, 0, mPendingKeyMetaState,
+                        mCurrentDeviceId, 0, KeyEvent.FLAG_FALLBACK,
+                        mCurrentSource));
+                mPendingKeyCode = KeyEvent.KEYCODE_UNKNOWN;
+            }
+        }
+
+        private boolean startFling(long time, float vx, float vy) {
+            if (LOCAL_DEBUG) {
+                Log.d(LOCAL_TAG, "Considering fling: vx=" + vx + ", vy=" + vy
+                        + ", min=" + mConfigMinFlingVelocity);
+            }
+
+            // Flings must be oriented in the same direction as the preceding movements.
+            switch (mPendingKeyCode) {
+                case KeyEvent.KEYCODE_DPAD_LEFT:
+                    if (-vx >= mConfigMinFlingVelocity
+                            && Math.abs(vy) < mConfigMinFlingVelocity) {
+                        mFlingVelocity = -vx;
+                        break;
+                    }
+                    return false;
+
+                case KeyEvent.KEYCODE_DPAD_RIGHT:
+                    if (vx >= mConfigMinFlingVelocity
+                            && Math.abs(vy) < mConfigMinFlingVelocity) {
+                        mFlingVelocity = vx;
+                        break;
+                    }
+                    return false;
+
+                case KeyEvent.KEYCODE_DPAD_UP:
+                    if (-vy >= mConfigMinFlingVelocity
+                            && Math.abs(vx) < mConfigMinFlingVelocity) {
+                        mFlingVelocity = -vy;
+                        break;
+                    }
+                    return false;
+
+                case KeyEvent.KEYCODE_DPAD_DOWN:
+                    if (vy >= mConfigMinFlingVelocity
+                            && Math.abs(vx) < mConfigMinFlingVelocity) {
+                        mFlingVelocity = vy;
+                        break;
+                    }
+                    return false;
+            }
+
+            // Post the first fling event.
+            mFlinging = postFling(time);
+            return mFlinging;
+        }
+
+        private boolean postFling(long time) {
+            // The idea here is to estimate the time when the pointer would have
+            // traveled one tick distance unit given the current fling velocity.
+            // This effect creates continuity of motion.
+            if (mFlingVelocity >= mConfigMinFlingVelocity) {
+                long delay = (long)(mConfigTickDistance / mFlingVelocity * 1000);
+                postAtTime(mFlingRunnable, time + delay);
+                if (LOCAL_DEBUG) {
+                    Log.d(LOCAL_TAG, "Posted fling: velocity="
+                            + mFlingVelocity + ", delay=" + delay
+                            + ", keyCode=" + mPendingKeyCode);
+                }
+                return true;
+            }
+            return false;
+        }
+
+        private void cancelFling() {
+            if (mFlinging) {
+                removeCallbacks(mFlingRunnable);
+                mFlinging = false;
+            }
+        }
+
+        private final Runnable mFlingRunnable = new Runnable() {
+            @Override
+            public void run() {
+                final long time = SystemClock.uptimeMillis();
+                sendKeyDownOrRepeat(time, mPendingKeyCode, mPendingKeyMetaState);
+                mFlingVelocity *= FLING_TICK_DECAY;
+                if (!postFling(time)) {
+                    mFlinging = false;
+                    finishKeys(time);
+                }
+            }
+        };
     }
 
     /**
@@ -5487,174 +6071,6 @@
         }
     }
 
-    /**
-     * Maintains state information for a single trackball axis, generating
-     * discrete (DPAD) movements based on raw trackball motion.
-     */
-    static final class TrackballAxis {
-        /**
-         * The maximum amount of acceleration we will apply.
-         */
-        static final float MAX_ACCELERATION = 20;
-
-        /**
-         * The maximum amount of time (in milliseconds) between events in order
-         * for us to consider the user to be doing fast trackball movements,
-         * and thus apply an acceleration.
-         */
-        static final long FAST_MOVE_TIME = 150;
-
-        /**
-         * Scaling factor to the time (in milliseconds) between events to how
-         * much to multiple/divide the current acceleration.  When movement
-         * is < FAST_MOVE_TIME this multiplies the acceleration; when >
-         * FAST_MOVE_TIME it divides it.
-         */
-        static final float ACCEL_MOVE_SCALING_FACTOR = (1.0f/40);
-
-        static final float FIRST_MOVEMENT_THRESHOLD = 0.5f;
-        static final float SECOND_CUMULATIVE_MOVEMENT_THRESHOLD = 2.0f;
-        static final float SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD = 1.0f;
-
-        float position;
-        float acceleration = 1;
-        long lastMoveTime = 0;
-        int step;
-        int dir;
-        int nonAccelMovement;
-
-        void reset(int _step) {
-            position = 0;
-            acceleration = 1;
-            lastMoveTime = 0;
-            step = _step;
-            dir = 0;
-        }
-
-        /**
-         * Add trackball movement into the state.  If the direction of movement
-         * has been reversed, the state is reset before adding the
-         * movement (so that you don't have to compensate for any previously
-         * collected movement before see the result of the movement in the
-         * new direction).
-         *
-         * @return Returns the absolute value of the amount of movement
-         * collected so far.
-         */
-        float collect(float off, long time, String axis) {
-            long normTime;
-            if (off > 0) {
-                normTime = (long)(off * FAST_MOVE_TIME);
-                if (dir < 0) {
-                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to positive!");
-                    position = 0;
-                    step = 0;
-                    acceleration = 1;
-                    lastMoveTime = 0;
-                }
-                dir = 1;
-            } else if (off < 0) {
-                normTime = (long)((-off) * FAST_MOVE_TIME);
-                if (dir > 0) {
-                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to negative!");
-                    position = 0;
-                    step = 0;
-                    acceleration = 1;
-                    lastMoveTime = 0;
-                }
-                dir = -1;
-            } else {
-                normTime = 0;
-            }
-
-            // The number of milliseconds between each movement that is
-            // considered "normal" and will not result in any acceleration
-            // or deceleration, scaled by the offset we have here.
-            if (normTime > 0) {
-                long delta = time - lastMoveTime;
-                lastMoveTime = time;
-                float acc = acceleration;
-                if (delta < normTime) {
-                    // The user is scrolling rapidly, so increase acceleration.
-                    float scale = (normTime-delta) * ACCEL_MOVE_SCALING_FACTOR;
-                    if (scale > 1) acc *= scale;
-                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " accelerate: off="
-                            + off + " normTime=" + normTime + " delta=" + delta
-                            + " scale=" + scale + " acc=" + acc);
-                    acceleration = acc < MAX_ACCELERATION ? acc : MAX_ACCELERATION;
-                } else {
-                    // The user is scrolling slowly, so decrease acceleration.
-                    float scale = (delta-normTime) * ACCEL_MOVE_SCALING_FACTOR;
-                    if (scale > 1) acc /= scale;
-                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " deccelerate: off="
-                            + off + " normTime=" + normTime + " delta=" + delta
-                            + " scale=" + scale + " acc=" + acc);
-                    acceleration = acc > 1 ? acc : 1;
-                }
-            }
-            position += off;
-            return Math.abs(position);
-        }
-
-        /**
-         * Generate the number of discrete movement events appropriate for
-         * the currently collected trackball movement.
-         *
-         * @return Returns the number of discrete movements, either positive
-         * or negative, or 0 if there is not enough trackball movement yet
-         * for a discrete movement.
-         */
-        int generate() {
-            int movement = 0;
-            nonAccelMovement = 0;
-            do {
-                final int dir = position >= 0 ? 1 : -1;
-                switch (step) {
-                    // If we are going to execute the first step, then we want
-                    // to do this as soon as possible instead of waiting for
-                    // a full movement, in order to make things look responsive.
-                    case 0:
-                        if (Math.abs(position) < FIRST_MOVEMENT_THRESHOLD) {
-                            return movement;
-                        }
-                        movement += dir;
-                        nonAccelMovement += dir;
-                        step = 1;
-                        break;
-                    // If we have generated the first movement, then we need
-                    // to wait for the second complete trackball motion before
-                    // generating the second discrete movement.
-                    case 1:
-                        if (Math.abs(position) < SECOND_CUMULATIVE_MOVEMENT_THRESHOLD) {
-                            return movement;
-                        }
-                        movement += dir;
-                        nonAccelMovement += dir;
-                        position -= SECOND_CUMULATIVE_MOVEMENT_THRESHOLD * dir;
-                        step = 2;
-                        break;
-                    // After the first two, we generate discrete movements
-                    // consistently with the trackball, applying an acceleration
-                    // if the trackball is moving quickly.  This is a simple
-                    // acceleration on top of what we already compute based
-                    // on how quickly the wheel is being turned, to apply
-                    // a longer increasing acceleration to continuous movement
-                    // in one direction.
-                    default:
-                        if (Math.abs(position) < SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD) {
-                            return movement;
-                        }
-                        movement += dir;
-                        position -= dir * SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD;
-                        float acc = acceleration;
-                        acc *= 1.1f;
-                        acceleration = acc < MAX_ACCELERATION ? acc : acceleration;
-                        break;
-                }
-            } while (true);
-        }
-    }
-
     public static final class CalledFromWrongThreadException extends AndroidRuntimeException {
         public CalledFromWrongThreadException(String msg) {
             super(msg);
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index d901d0a..8ae0021 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1004,6 +1004,7 @@
      * @param flag true if plugins should be enabled
      * @deprecated This method has been deprecated in favor of
      *             {@link #setPluginState}
+     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}
      */
     @Deprecated
     public synchronized void setPluginsEnabled(boolean flag) {
@@ -1032,6 +1033,7 @@
      * @param pluginsPath a String path to the directory containing plugins
      * @deprecated This method is no longer used as plugins are loaded from
      *             their own APK via the system's package manager.
+     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}
      */
     @Deprecated
     public synchronized void setPluginsPath(String pluginsPath) {
@@ -1224,6 +1226,7 @@
      * @return true if plugins are enabled
      * @see #setPluginsEnabled
      * @deprecated This method has been replaced by {@link #getPluginState}
+     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}
      */
     @Deprecated
     public synchronized boolean getPluginsEnabled() {
@@ -1249,6 +1252,7 @@
      * @return an empty string
      * @deprecated This method is no longer used as plugins are loaded from
      * their own APK via the system's package manager.
+     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}
      */
     @Deprecated
     public synchronized String getPluginsPath() {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 3fa0940..94dadb4 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2606,7 +2606,7 @@
             mGlobalLayoutListenerAddedFilter = false;
         }
 
-        if (mAdapter != null) {
+        if (mAdapter != null && mDataSetObserver != null) {
             mAdapter.unregisterDataSetObserver(mDataSetObserver);
             mDataSetObserver = null;
         }
diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
index b620568..04931e7 100644
--- a/core/java/com/android/internal/widget/ScrollingTabContainerView.java
+++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
@@ -179,6 +179,9 @@
                 animateToTab(position);
             }
         }
+        if (mTabSpinner != null && position >= 0) {
+            mTabSpinner.setSelection(position);
+        }
     }
 
     public void setContentHeight(int contentHeight) {
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 66cea9d7..3e5586e 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -179,6 +179,7 @@
 	libandroidfw \
 	libexpat \
 	libnativehelper \
+	liblog \
 	libcutils \
 	libutils \
 	libbinder \
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index 9a9f6c8..11c7053 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -77,7 +77,14 @@
     }
 
     static SkCanvas* initRaster(JNIEnv* env, jobject, SkBitmap* bitmap) {
-        return bitmap ? new SkCanvas(*bitmap) : new SkCanvas;
+        if (bitmap) {
+            return new SkCanvas(*bitmap);
+        } else {
+            // Create an empty bitmap device to prevent callers from crashing
+            // if they attempt to draw into this canvas.
+            SkBitmap emptyBitmap;
+            return new SkCanvas(emptyBitmap);
+        }
     }
     
     static void copyCanvasState(JNIEnv* env, jobject clazz,
diff --git a/core/jni/android_os_Trace.cpp b/core/jni/android_os_Trace.cpp
index f028c86d..00ecd0a 100644
--- a/core/jni/android_os_Trace.cpp
+++ b/core/jni/android_os_Trace.cpp
@@ -18,6 +18,9 @@
 
 #include <JNIHelp.h>
 #include <ScopedUtfChars.h>
+#include <ScopedStringChars.h>
+
+#include <utils/String8.h>
 
 #include <cutils/trace.h>
 #include <cutils/log.h>
@@ -36,8 +39,20 @@
 
 static void android_os_Trace_nativeTraceBegin(JNIEnv* env, jclass clazz,
         jlong tag, jstring nameStr) {
-    ScopedUtfChars name(env, nameStr);
-    atrace_begin(tag, name.c_str());
+    const size_t MAX_SECTION_NAME_LEN = 127;
+    ScopedStringChars jchars(env, nameStr);
+    String8 utf8Chars(reinterpret_cast<const char16_t*>(jchars.get()),
+            jchars.size());
+    size_t size = utf8Chars.size();
+    char* str = utf8Chars.lockBuffer(size);
+    for (size_t i = 0; i < size; i++) {
+        char c = str[i];
+        if (c == '\0' || c == '\n' || c == '|') {
+            str[i] = ' ';
+        }
+    }
+    utf8Chars.unlockBuffer();
+    atrace_begin(tag, utf8Chars.string());
 }
 
 static void android_os_Trace_nativeTraceEnd(JNIEnv* env, jclass clazz,
@@ -45,6 +60,11 @@
     atrace_end(tag);
 }
 
+static void android_os_Trace_nativeSetAppTracingAllowed(JNIEnv* env,
+        jclass clazz, jboolean allowed) {
+    atrace_set_debuggable(allowed);
+}
+
 static JNINativeMethod gTraceMethods[] = {
     /* name, signature, funcPtr */
     { "nativeGetEnabledTags",
@@ -59,6 +79,9 @@
     { "nativeTraceEnd",
             "(J)V",
             (void*)android_os_Trace_nativeTraceEnd },
+    { "nativeSetAppTracingAllowed",
+            "(Z)V",
+            (void*)android_os_Trace_nativeSetAppTracingAllowed },
 };
 
 int register_android_os_Trace(JNIEnv* env) {
diff --git a/core/res/res/anim/rotation_animation_xfade_exit.xml b/core/res/res/anim/rotation_animation_xfade_exit.xml
index 7300724..1dedde4 100644
--- a/core/res/res/anim/rotation_animation_xfade_exit.xml
+++ b/core/res/res/anim/rotation_animation_xfade_exit.xml
@@ -18,6 +18,6 @@
 
 <set xmlns:android="http://schemas.android.com/apk/res/android">
     <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
-            android:duration="500"
+            android:duration="150"
             android:interpolator="@interpolator/decelerate_quad" />
 </set>
diff --git a/core/res/res/drawable-hdpi/menu_popup_panel_holo_dark.9.png b/core/res/res/drawable-hdpi/menu_popup_panel_holo_dark.9.png
new file mode 100644
index 0000000..e5ff886
--- /dev/null
+++ b/core/res/res/drawable-hdpi/menu_popup_panel_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/menu_popup_panel_holo_light.9.png b/core/res/res/drawable-hdpi/menu_popup_panel_holo_light.9.png
new file mode 100644
index 0000000..06d1653
--- /dev/null
+++ b/core/res/res/drawable-hdpi/menu_popup_panel_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/menu_popup_panel_holo_dark.9.png b/core/res/res/drawable-mdpi/menu_popup_panel_holo_dark.9.png
new file mode 100644
index 0000000..2020a42
--- /dev/null
+++ b/core/res/res/drawable-mdpi/menu_popup_panel_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/menu_popup_panel_holo_light.9.png b/core/res/res/drawable-mdpi/menu_popup_panel_holo_light.9.png
new file mode 100644
index 0000000..7cae402
--- /dev/null
+++ b/core/res/res/drawable-mdpi/menu_popup_panel_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/menu_popup_panel_holo_dark.9.png b/core/res/res/drawable-xhdpi/menu_popup_panel_holo_dark.9.png
new file mode 100644
index 0000000..e85b0c2
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/menu_popup_panel_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/menu_popup_panel_holo_light.9.png b/core/res/res/drawable-xhdpi/menu_popup_panel_holo_light.9.png
new file mode 100644
index 0000000..eea215d
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/menu_popup_panel_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable/menu_panel_holo_dark.xml b/core/res/res/drawable/menu_panel_holo_dark.xml
new file mode 100644
index 0000000..658a3ac
--- /dev/null
+++ b/core/res/res/drawable/menu_panel_holo_dark.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_above_anchor="true" android:drawable="@android:drawable/menu_popup_panel_holo_dark" />
+    <item android:drawable="@android:drawable/menu_dropdown_panel_holo_dark" />
+</selector>
diff --git a/core/res/res/drawable/menu_panel_holo_light.xml b/core/res/res/drawable/menu_panel_holo_light.xml
new file mode 100644
index 0000000..a37e934
--- /dev/null
+++ b/core/res/res/drawable/menu_panel_holo_light.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_above_anchor="true" android:drawable="@android:drawable/menu_popup_panel_holo_light" />
+    <item android:drawable="@android:drawable/menu_dropdown_panel_holo_light" />
+</selector>
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index f7ff77b..146607e 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -141,6 +141,10 @@
        <item>@drawable/menu_submenu_background</item>
        <item>@drawable/menu_dropdown_panel_holo_light</item>
        <item>@drawable/menu_dropdown_panel_holo_dark</item>
+       <item>@drawable/menu_popup_panel_holo_light</item>
+       <item>@drawable/menu_popup_panel_holo_dark</item>
+       <item>@drawable/menu_panel_holo_light</item>
+       <item>@drawable/menu_panel_holo_dark</item>
        <item>@drawable/overscroll_edge</item>
        <item>@drawable/overscroll_glow</item>
        <item>@drawable/spinner_16_outer_holo</item>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 56c2235..f494d8c 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1869,7 +1869,7 @@
 
     <style name="Widget.Holo.ListPopupWindow" parent="Widget.ListPopupWindow">
         <item name="android:dropDownSelector">@android:drawable/list_selector_holo_dark</item>
-        <item name="android:popupBackground">@android:drawable/menu_dropdown_panel_holo_dark</item>
+        <item name="android:popupBackground">@android:drawable/menu_panel_holo_dark</item>
         <item name="android:dropDownVerticalOffset">0dip</item>
         <item name="android:dropDownHorizontalOffset">0dip</item>
         <item name="android:dropDownWidth">wrap_content</item>
@@ -2242,7 +2242,7 @@
 
     <style name="Widget.Holo.Light.ListPopupWindow" parent="Widget.ListPopupWindow">
         <item name="android:dropDownSelector">@android:drawable/list_selector_holo_light</item>
-        <item name="android:popupBackground">@android:drawable/menu_dropdown_panel_holo_light</item>
+        <item name="android:popupBackground">@android:drawable/menu_panel_holo_light</item>
         <item name="android:dropDownVerticalOffset">0dip</item>
         <item name="android:dropDownHorizontalOffset">0dip</item>
         <item name="android:dropDownWidth">wrap_content</item>
diff --git a/drm/jni/Android.mk b/drm/jni/Android.mk
index fff7eee..474b9b2 100644
--- a/drm/jni/Android.mk
+++ b/drm/jni/Android.mk
@@ -23,6 +23,7 @@
 
 LOCAL_SHARED_LIBRARIES := \
     libdrmframework \
+    liblog \
     libutils \
     libandroid_runtime \
     libnativehelper \
@@ -43,4 +44,3 @@
 LOCAL_MODULE_TAGS := optional
 
 include $(BUILD_SHARED_LIBRARY)
-
diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index 8bc6b96..5751331 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -500,7 +500,9 @@
      */
     public void copyFromUnchecked(int[] d) {
         mRS.validate();
-        if (mCurrentDimY > 0) {
+        if (mCurrentDimZ > 0) {
+            copy3DRangeFromUnchecked(0, 0, 0, mCurrentDimX, mCurrentDimY, mCurrentDimZ, d);
+        } else if (mCurrentDimY > 0) {
             copy2DRangeFromUnchecked(0, 0, mCurrentDimX, mCurrentDimY, d);
         } else {
             copy1DRangeFromUnchecked(0, mCurrentCount, d);
@@ -515,7 +517,9 @@
      */
     public void copyFromUnchecked(short[] d) {
         mRS.validate();
-        if (mCurrentDimY > 0) {
+        if (mCurrentDimZ > 0) {
+            copy3DRangeFromUnchecked(0, 0, 0, mCurrentDimX, mCurrentDimY, mCurrentDimZ, d);
+        } else if (mCurrentDimY > 0) {
             copy2DRangeFromUnchecked(0, 0, mCurrentDimX, mCurrentDimY, d);
         } else {
             copy1DRangeFromUnchecked(0, mCurrentCount, d);
@@ -530,7 +534,9 @@
      */
     public void copyFromUnchecked(byte[] d) {
         mRS.validate();
-        if (mCurrentDimY > 0) {
+        if (mCurrentDimZ > 0) {
+            copy3DRangeFromUnchecked(0, 0, 0, mCurrentDimX, mCurrentDimY, mCurrentDimZ, d);
+        } else if (mCurrentDimY > 0) {
             copy2DRangeFromUnchecked(0, 0, mCurrentDimX, mCurrentDimY, d);
         } else {
             copy1DRangeFromUnchecked(0, mCurrentCount, d);
@@ -545,7 +551,9 @@
      */
     public void copyFromUnchecked(float[] d) {
         mRS.validate();
-        if (mCurrentDimY > 0) {
+        if (mCurrentDimZ > 0) {
+            copy3DRangeFromUnchecked(0, 0, 0, mCurrentDimX, mCurrentDimY, mCurrentDimZ, d);
+        } else if (mCurrentDimY > 0) {
             copy2DRangeFromUnchecked(0, 0, mCurrentDimX, mCurrentDimY, d);
         } else {
             copy1DRangeFromUnchecked(0, mCurrentCount, d);
@@ -561,7 +569,9 @@
      */
     public void copyFrom(int[] d) {
         mRS.validate();
-        if (mCurrentDimY > 0) {
+        if (mCurrentDimZ > 0) {
+            copy3DRangeFrom(0, 0, 0, mCurrentDimX, mCurrentDimY, mCurrentDimZ, d);
+        } else if (mCurrentDimY > 0) {
             copy2DRangeFrom(0, 0, mCurrentDimX, mCurrentDimY, d);
         } else {
             copy1DRangeFrom(0, mCurrentCount, d);
@@ -577,7 +587,9 @@
      */
     public void copyFrom(short[] d) {
         mRS.validate();
-        if (mCurrentDimY > 0) {
+        if (mCurrentDimZ > 0) {
+            copy3DRangeFrom(0, 0, 0, mCurrentDimX, mCurrentDimY, mCurrentDimZ, d);
+        } else if (mCurrentDimY > 0) {
             copy2DRangeFrom(0, 0, mCurrentDimX, mCurrentDimY, d);
         } else {
             copy1DRangeFrom(0, mCurrentCount, d);
@@ -593,7 +605,9 @@
      */
     public void copyFrom(byte[] d) {
         mRS.validate();
-        if (mCurrentDimY > 0) {
+        if (mCurrentDimZ > 0) {
+            copy3DRangeFrom(0, 0, 0, mCurrentDimX, mCurrentDimY, mCurrentDimZ, d);
+        } else if (mCurrentDimY > 0) {
             copy2DRangeFrom(0, 0, mCurrentDimX, mCurrentDimY, d);
         } else {
             copy1DRangeFrom(0, mCurrentCount, d);
@@ -609,7 +623,9 @@
      */
     public void copyFrom(float[] d) {
         mRS.validate();
-        if (mCurrentDimY > 0) {
+        if (mCurrentDimZ > 0) {
+            copy3DRangeFrom(0, 0, 0, mCurrentDimX, mCurrentDimY, mCurrentDimZ, d);
+        } else if (mCurrentDimY > 0) {
             copy2DRangeFrom(0, 0, mCurrentDimX, mCurrentDimY, d);
         } else {
             copy1DRangeFrom(0, mCurrentCount, d);
@@ -975,12 +991,144 @@
             Canvas c = new Canvas(newBitmap);
             c.drawBitmap(data, 0, 0, null);
             copy2DRangeFrom(xoff, yoff, newBitmap);
+            return;
         }
         validateBitmapFormat(data);
         validate2DRange(xoff, yoff, data.getWidth(), data.getHeight());
         mRS.nAllocationData2D(getIDSafe(), xoff, yoff, mSelectedLOD, mSelectedFace.mID, data);
     }
 
+    private void validate3DRange(int xoff, int yoff, int zoff, int w, int h, int d) {
+        if (mAdaptedAllocation != null) {
+
+        } else {
+
+            if (xoff < 0 || yoff < 0 || zoff < 0) {
+                throw new RSIllegalArgumentException("Offset cannot be negative.");
+            }
+            if (h < 0 || w < 0 || d < 0) {
+                throw new RSIllegalArgumentException("Height or width cannot be negative.");
+            }
+            if (((xoff + w) > mCurrentDimX) || ((yoff + h) > mCurrentDimY) || ((zoff + d) > mCurrentDimZ)) {
+                throw new RSIllegalArgumentException("Updated region larger than allocation.");
+            }
+        }
+    }
+
+    /**
+     * @hide
+     *
+     */
+    void copy3DRangeFromUnchecked(int xoff, int yoff, int zoff, int w, int h, int d, byte[] data) {
+        mRS.validate();
+        validate3DRange(xoff, yoff, zoff, w, h, d);
+        mRS.nAllocationData3D(getIDSafe(), xoff, yoff, zoff, mSelectedLOD,
+                              w, h, d, data, data.length);
+    }
+
+    /**
+     * @hide
+     *
+     */
+    void copy3DRangeFromUnchecked(int xoff, int yoff, int zoff, int w, int h, int d, short[] data) {
+        mRS.validate();
+        validate3DRange(xoff, yoff, zoff, w, h, d);
+        mRS.nAllocationData3D(getIDSafe(), xoff, yoff, zoff, mSelectedLOD,
+                              w, h, d, data, data.length * 2);
+    }
+
+    /**
+     * @hide
+     *
+     */
+    void copy3DRangeFromUnchecked(int xoff, int yoff, int zoff, int w, int h, int d, int[] data) {
+        mRS.validate();
+        validate3DRange(xoff, yoff, zoff, w, h, d);
+        mRS.nAllocationData3D(getIDSafe(), xoff, yoff, zoff, mSelectedLOD,
+                              w, h, d, data, data.length * 4);
+    }
+
+    /**
+     * @hide
+     *
+     */
+    void copy3DRangeFromUnchecked(int xoff, int yoff, int zoff, int w, int h, int d, float[] data) {
+        mRS.validate();
+        validate3DRange(xoff, yoff, zoff, w, h, d);
+        mRS.nAllocationData3D(getIDSafe(), xoff, yoff, zoff, mSelectedLOD,
+                              w, h, d, data, data.length * 4);
+    }
+
+
+    /**
+     * @hide
+     * Copy a rectangular region from the array into the allocation.
+     * The incoming array is assumed to be tightly packed.
+     *
+     * @param xoff X offset of the region to update
+     * @param yoff Y offset of the region to update
+     * @param zoff Z offset of the region to update
+     * @param w Width of the incoming region to update
+     * @param h Height of the incoming region to update
+     * @param d Depth of the incoming region to update
+     * @param data to be placed into the allocation
+     */
+    public void copy3DRangeFrom(int xoff, int yoff, int zoff, int w, int h, int d, byte[] data) {
+        validateIsInt8();
+        copy3DRangeFromUnchecked(xoff, yoff, zoff, w, h, d, data);
+    }
+
+    /**
+     * @hide
+     *
+     */
+    public void copy3DRangeFrom(int xoff, int yoff, int zoff, int w, int h, int d, short[] data) {
+        validateIsInt16();
+        copy3DRangeFromUnchecked(xoff, yoff, zoff, w, h, d, data);
+    }
+
+    /**
+     * @hide
+     *
+     */
+    public void copy3DRangeFrom(int xoff, int yoff, int zoff, int w, int h, int d, int[] data) {
+        validateIsInt32();
+        copy3DRangeFromUnchecked(xoff, yoff, zoff, w, h, d, data);
+    }
+
+    /**
+     * @hide
+     *
+     */
+    public void copy3DRangeFrom(int xoff, int yoff, int zoff, int w, int h, int d, float[] data) {
+        validateIsFloat32();
+        copy3DRangeFromUnchecked(xoff, yoff, zoff, w, h, d, data);
+    }
+
+    /**
+     * @hide
+     * Copy a rectangular region into the allocation from another
+     * allocation.
+     *
+     * @param xoff X offset of the region to update.
+     * @param yoff Y offset of the region to update.
+     * @param w Width of the incoming region to update.
+     * @param h Height of the incoming region to update.
+     * @param d Depth of the incoming region to update.
+     * @param data source allocation.
+     * @param dataXoff X offset in data of the region to update.
+     * @param dataYoff Y offset in data of the region to update.
+     * @param dataZoff Z offset in data of the region to update
+     */
+    public void copy3DRangeFrom(int xoff, int yoff, int zoff, int w, int h, int d,
+                                Allocation data, int dataXoff, int dataYoff, int dataZoff) {
+        mRS.validate();
+        validate3DRange(xoff, yoff, zoff, w, h, d);
+        mRS.nAllocationData3D(getIDSafe(), xoff, yoff, zoff, mSelectedLOD,
+                              w, h, d, data.getID(mRS), dataXoff, dataYoff, dataZoff,
+                              data.mSelectedLOD);
+    }
+
 
     /**
      * Copy from the Allocation into a Bitmap.  The bitmap must
@@ -1058,6 +1206,10 @@
      * A new type will be created with the new dimension.
      *
      * @param dimX The new size of the allocation.
+     *
+     * @deprecated Renderscript objects should be immutable once
+     * created.  The replacement is to create a new allocation and copy the
+     * contents.
      */
     public synchronized void resize(int dimX) {
         if ((mType.getY() > 0)|| (mType.getZ() > 0) || mType.hasFaces() || mType.hasMipmaps()) {
@@ -1072,38 +1224,6 @@
         updateCacheInfo(mType);
     }
 
-    /**
-     * Resize a 2D allocation.  The contents of the allocation are
-     * preserved.  If new elements are allocated objects are created
-     * with null contents and the new region is otherwise undefined.
-     *
-     * If the new region is smaller the references of any objects
-     * outside the new region will be released.
-     *
-     * A new type will be created with the new dimension.
-     *
-     * @param dimX The new size of the allocation.
-     * @param dimY The new size of the allocation.
-     */
-    public synchronized void resize(int dimX, int dimY) {
-        if ((mType.getZ() > 0) || mType.hasFaces() || mType.hasMipmaps()) {
-            throw new RSInvalidStateException(
-                "Resize only support for 2D allocations at this time.");
-        }
-        if (mType.getY() == 0) {
-            throw new RSInvalidStateException(
-                "Resize only support for 2D allocations at this time.");
-        }
-        mRS.nAllocationResize2D(getID(mRS), dimX, dimY);
-        mRS.finish();  // Necessary because resize is fifoed and update is async.
-
-        int typeID = mRS.nAllocationGetType(getID(mRS));
-        mType = new Type(typeID, mRS);
-        mType.updateFromNative();
-        updateCacheInfo(mType);
-    }
-
-
 
     // creation
 
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index 60f381c..d5af276 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -83,11 +83,7 @@
     native void nContextInitToClient(int con);
     native void nContextDeinitToClient(int con);
 
-    /**
-     * Name of the file that holds the object cache.
-     */
-    private static final String CACHE_PATH = "com.android.renderscript.cache";
-    static String mCachePath;
+    static File mCacheDir;
 
      /**
      * Sets the directory to use as a persistent storage for the
@@ -97,9 +93,8 @@
      * @param cacheDir A directory the current process can write to
      */
     public static void setupDiskCache(File cacheDir) {
-        File f = new File(cacheDir, CACHE_PATH);
-        mCachePath = f.getAbsolutePath();
-        f.mkdirs();
+        // Defer creation of cache path to nScriptCCreate().
+        mCacheDir = cacheDir;
     }
 
     public enum ContextType {
@@ -420,6 +415,46 @@
         rsnAllocationData2D(mContext, id, xoff, yoff, mip, face, b);
     }
 
+    native void rsnAllocationData3D(int con,
+                                    int dstAlloc, int dstXoff, int dstYoff, int dstZoff,
+                                    int dstMip,
+                                    int width, int height, int depth,
+                                    int srcAlloc, int srcXoff, int srcYoff, int srcZoff,
+                                    int srcMip);
+    synchronized void nAllocationData3D(int dstAlloc, int dstXoff, int dstYoff, int dstZoff,
+                                        int dstMip,
+                                        int width, int height, int depth,
+                                        int srcAlloc, int srcXoff, int srcYoff, int srcZoff,
+                                        int srcMip) {
+        validate();
+        rsnAllocationData3D(mContext,
+                            dstAlloc, dstXoff, dstYoff, dstZoff,
+                            dstMip, width, height, depth,
+                            srcAlloc, srcXoff, srcYoff, srcZoff, srcMip);
+    }
+
+    native void rsnAllocationData3D(int con, int id, int xoff, int yoff, int zoff, int mip, int w, int h, int depth, byte[] d, int sizeBytes);
+    synchronized void nAllocationData3D(int id, int xoff, int yoff, int zoff, int mip, int w, int h, int depth, byte[] d, int sizeBytes) {
+        validate();
+        rsnAllocationData3D(mContext, id, xoff, yoff, zoff, mip, w, h, depth, d, sizeBytes);
+    }
+    native void rsnAllocationData3D(int con, int id, int xoff, int yoff, int zoff, int mip, int w, int h, int depth, short[] d, int sizeBytes);
+    synchronized void nAllocationData3D(int id, int xoff, int yoff, int zoff, int mip, int w, int h, int depth, short[] d, int sizeBytes) {
+        validate();
+        rsnAllocationData3D(mContext, id, xoff, yoff, zoff, mip, w, h, depth, d, sizeBytes);
+    }
+    native void rsnAllocationData3D(int con, int id, int xoff, int yoff, int zoff, int mip, int w, int h, int depth, int[] d, int sizeBytes);
+    synchronized void nAllocationData3D(int id, int xoff, int yoff, int zoff, int mip, int w, int h, int depth, int[] d, int sizeBytes) {
+        validate();
+        rsnAllocationData3D(mContext, id, xoff, yoff, zoff, mip, w, h, depth, d, sizeBytes);
+    }
+    native void rsnAllocationData3D(int con, int id, int xoff, int yoff, int zoff, int mip, int w, int h, int depth, float[] d, int sizeBytes);
+    synchronized void nAllocationData3D(int id, int xoff, int yoff, int zoff, int mip, int w, int h, int depth, float[] d, int sizeBytes) {
+        validate();
+        rsnAllocationData3D(mContext, id, xoff, yoff, zoff, mip, w, h, depth, d, sizeBytes);
+    }
+
+
     native void rsnAllocationRead(int con, int id, byte[] d);
     synchronized void nAllocationRead(int id, byte[] d) {
         validate();
@@ -451,11 +486,6 @@
         validate();
         rsnAllocationResize1D(mContext, id, dimX);
     }
-    native void rsnAllocationResize2D(int con, int id, int dimX, int dimY);
-    synchronized void nAllocationResize2D(int id, int dimX, int dimY) {
-        validate();
-        rsnAllocationResize2D(mContext, id, dimX, dimY);
-    }
 
     native int  rsnFileA3DCreateFromAssetStream(int con, int assetStream);
     synchronized int nFileA3DCreateFromAssetStream(int assetStream) {
diff --git a/graphics/java/android/renderscript/ScriptC.java b/graphics/java/android/renderscript/ScriptC.java
index 9221c7a..2f69775 100644
--- a/graphics/java/android/renderscript/ScriptC.java
+++ b/graphics/java/android/renderscript/ScriptC.java
@@ -64,6 +64,12 @@
         rs.addAllocSizeForGC(mGCSize);
     }
 
+    /**
+     * Name of the file that holds the object cache.
+     */
+    private static final String CACHE_PATH = "com.android.renderscript.cache";
+
+    static String mCachePath;
 
     private static synchronized int internalCreate(RenderScript rs, Resources resources, int resourceID) {
         byte[] pgm;
@@ -96,7 +102,13 @@
 
         String resName = resources.getResourceEntryName(resourceID);
 
+        // Create the RS cache path if we haven't done so already.
+        if (mCachePath == null) {
+            File f = new File(rs.mCacheDir, CACHE_PATH);
+            mCachePath = f.getAbsolutePath();
+            f.mkdirs();
+        }
         Log.v(TAG, "Create script for resource = " + resName);
-        return rs.nScriptCCreate(resName, rs.mCachePath, pgm, pgmLength);
+        return rs.nScriptCCreate(resName, mCachePath, pgm, pgmLength);
     }
 }
diff --git a/graphics/jni/Android.mk b/graphics/jni/Android.mk
index 80d7728..e8beae53 100644
--- a/graphics/jni/Android.mk
+++ b/graphics/jni/Android.mk
@@ -10,6 +10,7 @@
         libnativehelper \
         libRS \
         libcutils \
+        liblog \
         libskia \
         libutils \
         libui \
diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp
index 8757b19..460a516 100644
--- a/graphics/jni/android_renderscript_RenderScript.cpp
+++ b/graphics/jni/android_renderscript_RenderScript.cpp
@@ -725,6 +725,72 @@
 }
 
 static void
+nAllocationData3D_s(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint xoff, jint yoff, jint zoff, jint lod,
+                    jint w, jint h, jint d, jshortArray data, int sizeBytes)
+{
+    jint len = _env->GetArrayLength(data);
+    LOG_API("nAllocation3DData_s, con(%p), adapter(%p), xoff(%i), yoff(%i), w(%i), h(%i), len(%i)", con, (RsAllocation)alloc, xoff, yoff, zoff, w, h, d, len);
+    jshort *ptr = _env->GetShortArrayElements(data, NULL);
+    rsAllocation3DData(con, (RsAllocation)alloc, xoff, yoff, zoff, lod, w, h, d, ptr, sizeBytes, 0);
+    _env->ReleaseShortArrayElements(data, ptr, JNI_ABORT);
+}
+
+static void
+nAllocationData3D_b(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint xoff, jint yoff, jint zoff, jint lod,
+                    jint w, jint h, jint d, jbyteArray data, int sizeBytes)
+{
+    jint len = _env->GetArrayLength(data);
+    LOG_API("nAllocation3DData_b, con(%p), adapter(%p), xoff(%i), yoff(%i), w(%i), h(%i), len(%i)", con, (RsAllocation)alloc, xoff, yoff, zoff, w, h, d, len);
+    jbyte *ptr = _env->GetByteArrayElements(data, NULL);
+    rsAllocation3DData(con, (RsAllocation)alloc, xoff, yoff, zoff, lod, w, h, d, ptr, sizeBytes, 0);
+    _env->ReleaseByteArrayElements(data, ptr, JNI_ABORT);
+}
+
+static void
+nAllocationData3D_i(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint xoff, jint yoff, jint zoff, jint lod,
+                    jint w, jint h, jint d, jintArray data, int sizeBytes)
+{
+    jint len = _env->GetArrayLength(data);
+    LOG_API("nAllocation3DData_i, con(%p), adapter(%p), xoff(%i), yoff(%i), w(%i), h(%i), len(%i)", con, (RsAllocation)alloc, xoff, yoff, zoff, w, h, d, len);
+    jint *ptr = _env->GetIntArrayElements(data, NULL);
+    rsAllocation3DData(con, (RsAllocation)alloc, xoff, yoff, zoff, lod, w, h, d, ptr, sizeBytes, 0);
+    _env->ReleaseIntArrayElements(data, ptr, JNI_ABORT);
+}
+
+static void
+nAllocationData3D_f(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint xoff, jint yoff, jint zoff, jint lod,
+                    jint w, jint h, jint d, jfloatArray data, int sizeBytes)
+{
+    jint len = _env->GetArrayLength(data);
+    LOG_API("nAllocation3DData_f, con(%p), adapter(%p), xoff(%i), yoff(%i), w(%i), h(%i), len(%i)", con, (RsAllocation)alloc, xoff, yoff, zoff, w, h, d, len);
+    jfloat *ptr = _env->GetFloatArrayElements(data, NULL);
+    rsAllocation3DData(con, (RsAllocation)alloc, xoff, yoff, zoff, lod, w, h, d, ptr, sizeBytes, 0);
+    _env->ReleaseFloatArrayElements(data, ptr, JNI_ABORT);
+}
+
+static void
+nAllocationData3D_alloc(JNIEnv *_env, jobject _this, RsContext con,
+                        jint dstAlloc, jint dstXoff, jint dstYoff, jint dstZoff,
+                        jint dstMip,
+                        jint width, jint height, jint depth,
+                        jint srcAlloc, jint srcXoff, jint srcYoff, jint srcZoff,
+                        jint srcMip)
+{
+    LOG_API("nAllocationData3D_alloc, con(%p), dstAlloc(%p), dstXoff(%i), dstYoff(%i),"
+            " dstMip(%i), width(%i), height(%i),"
+            " srcAlloc(%p), srcXoff(%i), srcYoff(%i), srcMip(%i)",
+            con, (RsAllocation)dstAlloc, dstXoff, dstYoff, dstMip, dstFace,
+            width, height, (RsAllocation)srcAlloc, srcXoff, srcYoff, srcMip, srcFace);
+
+    rsAllocationCopy3DRange(con,
+                            (RsAllocation)dstAlloc,
+                            dstXoff, dstYoff, dstZoff, dstMip,
+                            width, height, depth,
+                            (RsAllocation)srcAlloc,
+                            srcXoff, srcYoff, srcZoff, srcMip);
+}
+
+static void
 nAllocationRead_i(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jintArray data)
 {
     jint len = _env->GetArrayLength(data);
@@ -782,13 +848,6 @@
     rsAllocationResize1D(con, (RsAllocation)alloc, dimX);
 }
 
-static void
-nAllocationResize2D(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint dimX, jint dimY)
-{
-    LOG_API("nAllocationResize1D, con(%p), alloc(%p), sizeX(%i), sizeY(%i)", con, (RsAllocation)alloc, dimX, dimY);
-    rsAllocationResize2D(con, (RsAllocation)alloc, dimX, dimY);
-}
-
 // -----------------------------------
 
 static int
@@ -1519,13 +1578,17 @@
 {"rsnAllocationData2D",              "(IIIIIIII[BI)V",                        (void*)nAllocationData2D_b },
 {"rsnAllocationData2D",              "(IIIIIIII[FI)V",                        (void*)nAllocationData2D_f },
 {"rsnAllocationData2D",              "(IIIIIIIIIIIII)V",                      (void*)nAllocationData2D_alloc },
+{"rsnAllocationData3D",              "(IIIIIIIII[II)V",                       (void*)nAllocationData3D_i },
+{"rsnAllocationData3D",              "(IIIIIIIII[SI)V",                       (void*)nAllocationData3D_s },
+{"rsnAllocationData3D",              "(IIIIIIIII[BI)V",                       (void*)nAllocationData3D_b },
+{"rsnAllocationData3D",              "(IIIIIIIII[FI)V",                       (void*)nAllocationData3D_f },
+{"rsnAllocationData3D",              "(IIIIIIIIIIIIII)V",                     (void*)nAllocationData3D_alloc },
 {"rsnAllocationRead",                "(II[I)V",                               (void*)nAllocationRead_i },
 {"rsnAllocationRead",                "(II[S)V",                               (void*)nAllocationRead_s },
 {"rsnAllocationRead",                "(II[B)V",                               (void*)nAllocationRead_b },
 {"rsnAllocationRead",                "(II[F)V",                               (void*)nAllocationRead_f },
 {"rsnAllocationGetType",             "(II)I",                                 (void*)nAllocationGetType},
 {"rsnAllocationResize1D",            "(III)V",                                (void*)nAllocationResize1D },
-{"rsnAllocationResize2D",            "(IIII)V",                               (void*)nAllocationResize2D },
 {"rsnAllocationGenerateMipmaps",     "(II)V",                                 (void*)nAllocationGenerateMipmaps },
 
 {"rsnScriptBindAllocation",          "(IIII)V",                               (void*)nScriptBindAllocation },
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 06e658d..7b59bf24 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -58,7 +58,7 @@
 
 	LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER -DGL_GLEXT_PROTOTYPES
 	LOCAL_MODULE_CLASS := SHARED_LIBRARIES
-	LOCAL_SHARED_LIBRARIES := libcutils libutils libGLESv2 libskia libui libRS libRScpp
+	LOCAL_SHARED_LIBRARIES := liblog libcutils libutils libGLESv2 libskia libui libRS libRScpp
 	LOCAL_MODULE := libhwui
 	LOCAL_MODULE_TAGS := optional
 
diff --git a/libs/hwui/Dither.cpp b/libs/hwui/Dither.cpp
index 9bc5c14..51f1e39 100644
--- a/libs/hwui/Dither.cpp
+++ b/libs/hwui/Dither.cpp
@@ -38,6 +38,10 @@
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
 
         if (useFloatTexture) {
+            // We use a R16F texture, let's remap the alpha channel to the
+            // red channel to avoid changing the shader sampling code on GL ES 3.0+
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_RED);
+
             float dither = 1.0f / (255.0f * DITHER_KERNEL_SIZE * DITHER_KERNEL_SIZE);
             const GLfloat pattern[] = {
                  0 * dither,  8 * dither,  2 * dither, 10 * dither,
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 3730017..e18d922 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1905,14 +1905,15 @@
 
 status_t OpenGLRenderer::drawDisplayList(DisplayList* displayList, Rect& dirty,
         int32_t replayFlags) {
+    status_t status;
     // All the usual checks and setup operations (quickReject, setupDraw, etc.)
     // will be performed by the display list itself
     if (displayList && displayList->isRenderable()) {
         if (CC_UNLIKELY(mCaches.drawDeferDisabled)) {
-            startFrame();
+            status = startFrame();
             ReplayStateStruct replayStruct(*this, dirty, replayFlags);
             displayList->replay(replayStruct, 0);
-            return replayStruct.mDrawGlStatus;
+            return status | replayStruct.mDrawGlStatus;
         }
 
         DeferredDisplayList deferredList;
@@ -1920,9 +1921,9 @@
         displayList->defer(deferStruct, 0);
 
         flushLayers();
-        startFrame();
+        status = startFrame();
 
-        return deferredList.flush(*this, dirty);
+        return status | deferredList.flush(*this, dirty);
     }
 
     return DrawGlInfo::kStatusDone;
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index 2479630..8eb85e5 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -186,7 +186,7 @@
         // ES 2.0
         "texture2D(ditherSampler, ditherTexCoords).a * " STR(DITHER_KERNEL_SIZE_INV_SQUARE),
         // ES 3.0
-        "texture2D(ditherSampler, ditherTexCoords).r"
+        "texture2D(ditherSampler, ditherTexCoords).a"
 };
 const char* gFS_Main_AddDitherToGradient =
         "    gradientColor += %s;\n";
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 773d7b6..637ac85 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -3534,6 +3534,9 @@
                     onNewPlaybackStateForRcc(msg.arg1 /* rccId */, msg.arg2 /* state */,
                             (RccPlaybackState)msg.obj /* newState */);
                     break;
+                case MSG_RCC_SEEK_REQUEST:
+                    onSetRemoteControlClientPlaybackPosition(msg.arg1 /* generationId */,
+                            ((Long)msg.obj).longValue() /* timeMs */);
 
                 case MSG_SET_RSX_CONNECTION_STATE:
                     onSetRsxConnectionState(msg.arg1/*available*/, msg.arg2/*address*/);
@@ -5867,7 +5870,16 @@
     }
 
     public void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) {
-        sendMsg(mAudioHandler, MSG_RCC_SEEK_REQUEST, SENDMSG_QUEUE, generationId /* arg1 */,
+        // ignore position change requests if invalid generation ID
+        synchronized(mRCStack) {
+            synchronized(mCurrentRcLock) {
+                if (mCurrentRcClientGen != generationId) {
+                    return;
+                }
+            }
+        }
+        // discard any unprocessed seek request in the message queue, and replace with latest
+        sendMsg(mAudioHandler, MSG_RCC_SEEK_REQUEST, SENDMSG_REPLACE, generationId /* arg1 */,
                 0 /* arg2 ignored*/, new Long(timeMs) /* obj */, 0 /* delay */);
     }
 
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index e076ef0..93bcf03 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -683,9 +683,12 @@
         /**
          * Called on the implementer to notify it that the playback head should be set at the given
          * position. If the position can be changed from its current value, the implementor of
-         * the interface should also update the playback position using
+         * the interface must also update the playback position using
          * {@link RemoteControlClient#setPlaybackState(int, long, int)} to reflect the actual new
          * position being used, regardless of whether it differs from the requested position.
+         * Failure to do so would cause the system to not know the new actual playback position,
+         * and user interface components would fail to show the user where playback resumed after
+         * the position was updated.
          * @param newPositionMs the new requested position in the current media, expressed in ms.
          */
         void onPlaybackPositionUpdate(long newPositionMs);
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index 6873060..416a2a1 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -28,6 +28,7 @@
     libmedia \
     libskia \
     libui \
+    liblog \
     libcutils \
     libgui \
     libstagefright \
diff --git a/media/jni/audioeffect/Android.mk b/media/jni/audioeffect/Android.mk
index b5d8b7b..3b1fb19 100644
--- a/media/jni/audioeffect/Android.mk
+++ b/media/jni/audioeffect/Android.mk
@@ -6,6 +6,7 @@
 	android_media_Visualizer.cpp
 
 LOCAL_SHARED_LIBRARIES := \
+	liblog \
 	libcutils \
 	libutils \
 	libandroid_runtime \
diff --git a/media/jni/mediaeditor/Android.mk b/media/jni/mediaeditor/Android.mk
index 040d2ab..6be7fdd 100644
--- a/media/jni/mediaeditor/Android.mk
+++ b/media/jni/mediaeditor/Android.mk
@@ -52,6 +52,7 @@
     libaudioutils \
     libbinder \
     libcutils \
+    liblog \
     libdl \
     libgui \
     libmedia \
diff --git a/media/jni/soundpool/Android.mk b/media/jni/soundpool/Android.mk
index 9b11bfa..5835b9f 100644
--- a/media/jni/soundpool/Android.mk
+++ b/media/jni/soundpool/Android.mk
@@ -5,6 +5,7 @@
 	android_media_SoundPool.cpp
 
 LOCAL_SHARED_LIBRARIES := \
+	liblog \
 	libcutils \
 	libutils \
 	libandroid_runtime \
diff --git a/media/libdrm/mobile1/Android.mk b/media/libdrm/mobile1/Android.mk
index b07d91c..7356f46 100644
--- a/media/libdrm/mobile1/Android.mk
+++ b/media/libdrm/mobile1/Android.mk
@@ -44,6 +44,7 @@
 LOCAL_SHARED_LIBRARIES :=   \
     libutils                \
     libcutils               \
+    liblog                  \
     libcrypto
 
 LOCAL_MODULE := libdrm1
@@ -69,12 +70,13 @@
     $(LOCAL_PATH)/include/parser \
     $(JNI_H_INCLUDE)    \
     $(call include-path-for, system-core)/cutils
-	
+
 
 LOCAL_SHARED_LIBRARIES := libdrm1 \
     libnativehelper               \
     libutils                      \
-    libcutils
+    libcutils                     \
+    liblog
 
 LOCAL_MODULE := libdrm1_jni
 
diff --git a/media/mca/filterfw/Android.mk b/media/mca/filterfw/Android.mk
index 1d69799..2a9448d 100644
--- a/media/mca/filterfw/Android.mk
+++ b/media/mca/filterfw/Android.mk
@@ -37,6 +37,7 @@
                           libdl \
                           libcutils \
                           libutils \
+                          liblog \
                           libandroid \
                           libjnigraphics \
                           libmedia
@@ -48,5 +49,3 @@
 LOCAL_PRELINK_MODULE := false
 
 include $(BUILD_SHARED_LIBRARY)
-
-
diff --git a/media/mca/filterpacks/Android.mk b/media/mca/filterpacks/Android.mk
index 6166b1e..6e54f60 100644
--- a/media/mca/filterpacks/Android.mk
+++ b/media/mca/filterpacks/Android.mk
@@ -46,10 +46,8 @@
                    native/imageproc/invert.c \
                    native/imageproc/to_rgba.c
 
-LOCAL_SHARED_LIBRARIES := libutils libfilterfw
+LOCAL_SHARED_LIBRARIES := liblog libutils libfilterfw
 
 LOCAL_PRELINK_MODULE := false
 
 include $(BUILD_SHARED_LIBRARY)
-
-
diff --git a/media/tests/omxjpegdecoder/Android.mk b/media/tests/omxjpegdecoder/Android.mk
index 9dcc7ba..ad874c8 100644
--- a/media/tests/omxjpegdecoder/Android.mk
+++ b/media/tests/omxjpegdecoder/Android.mk
@@ -29,6 +29,7 @@
     libstagefright_foundation \
     libbinder \
     libutils \
+    liblog \
     libjpeg
 
 LOCAL_C_INCLUDES := \
diff --git a/media/tests/players/Android.mk b/media/tests/players/Android.mk
index c655ae6..adf0d30 100644
--- a/media/tests/players/Android.mk
+++ b/media/tests/players/Android.mk
@@ -20,7 +20,8 @@
 
 LOCAL_SHARED_LIBRARIES:= \
     libbinder \
-    libutils
+    libutils \
+    liblog
 
 LOCAL_MODULE:= invoke_mock_media_player
 LOCAL_MODULE_TAGS := tests eng
diff --git a/native/android/Android.mk b/native/android/Android.mk
index 00d11da..207cc4b 100644
--- a/native/android/Android.mk
+++ b/native/android/Android.mk
@@ -17,6 +17,7 @@
     storage_manager.cpp
 
 LOCAL_SHARED_LIBRARIES := \
+    liblog \
     libcutils \
     libandroidfw \
     libutils \
diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java
index 5a2e261..daa5d13 100644
--- a/opengl/java/android/opengl/GLSurfaceView.java
+++ b/opengl/java/android/opengl/GLSurfaceView.java
@@ -18,7 +18,6 @@
 
 import java.io.Writer;
 import java.lang.ref.WeakReference;
-import java.util.ArrayList;
 
 import javax.microedition.khronos.egl.EGL10;
 import javax.microedition.khronos.egl.EGL11;
@@ -30,10 +29,13 @@
 import javax.microedition.khronos.opengles.GL10;
 
 import android.content.Context;
-import android.content.pm.ConfigurationInfo;
-import android.os.SystemProperties;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Trace;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.Choreographer;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 
@@ -164,11 +166,26 @@
     private final static String TAG = "GLSurfaceView";
     private final static boolean LOG_ATTACH_DETACH = false;
     private final static boolean LOG_THREADS = false;
-    private final static boolean LOG_PAUSE_RESUME = false;
     private final static boolean LOG_SURFACE = false;
     private final static boolean LOG_RENDERER = false;
     private final static boolean LOG_RENDERER_DRAW_FRAME = false;
     private final static boolean LOG_EGL = false;
+    private final static boolean TRACE_ENABLED = false;
+
+    private final WeakReference<GLSurfaceView> mThisWeakRef =
+            new WeakReference<GLSurfaceView>(this);
+    private GLThread mGLThread;
+    private Renderer mRenderer;
+    private boolean mDetached;
+    private EGLConfigChooser mEGLConfigChooser;
+    private EGLContextFactory mEGLContextFactory;
+    private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory;
+    private GLWrapper mGLWrapper;
+    private int mDebugFlags;
+    private int mEGLContextClientVersion;
+    private boolean mPreserveEGLContextOnPause;
+    private int mUserRenderMode;
+
     /**
      * The renderer only renders
      * when the surface is created, or when {@link #requestRender} is called.
@@ -241,13 +258,7 @@
         // underlying surface is created and destroyed
         SurfaceHolder holder = getHolder();
         holder.addCallback(this);
-        // setFormat is done by SurfaceView in SDK 2.3 and newer. Uncomment
-        // this statement if back-porting to 2.2 or older:
-        // holder.setFormat(PixelFormat.RGB_565);
-        //
-        // setType is not needed for SDK 2.0 or newer. Uncomment this
-        // statement if back-porting this code to older SDKs.
-        // holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
+        mUserRenderMode = RENDERMODE_CONTINUOUSLY;
     }
 
     /**
@@ -346,15 +357,16 @@
     public void setRenderer(Renderer renderer) {
         checkRenderThreadState();
         if (mEGLConfigChooser == null) {
-            mEGLConfigChooser = new SimpleEGLConfigChooser(true);
+            mEGLConfigChooser = new SimpleEGLConfigChooser(true, mEGLContextClientVersion);
         }
         if (mEGLContextFactory == null) {
-            mEGLContextFactory = new DefaultContextFactory();
+            mEGLContextFactory = new DefaultContextFactory(mEGLContextClientVersion);
         }
         if (mEGLWindowSurfaceFactory == null) {
             mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
         }
         mRenderer = renderer;
+
         mGLThread = new GLThread(mThisWeakRef);
         mGLThread.start();
     }
@@ -420,7 +432,7 @@
      * @param needDepth
      */
     public void setEGLConfigChooser(boolean needDepth) {
-        setEGLConfigChooser(new SimpleEGLConfigChooser(needDepth));
+        setEGLConfigChooser(new SimpleEGLConfigChooser(needDepth, mEGLContextClientVersion));
     }
 
     /**
@@ -439,7 +451,7 @@
     public void setEGLConfigChooser(int redSize, int greenSize, int blueSize,
             int alphaSize, int depthSize, int stencilSize) {
         setEGLConfigChooser(new ComponentSizeChooser(redSize, greenSize,
-                blueSize, alphaSize, depthSize, stencilSize));
+                blueSize, alphaSize, depthSize, stencilSize, mEGLContextClientVersion));
     }
 
     /**
@@ -466,6 +478,13 @@
      * If
      * {@link #setEGLConfigChooser(EGLConfigChooser)} has been called, then the supplied
      * EGLConfigChooser is responsible for choosing an OpenGL ES 2.0-compatible config.
+     *
+     * This method must be called before:
+     * <ul>
+     * <li>{@link #setEGLConfigChooser(boolean)}
+     * <li>{@link #setEGLConfigChooser(int, int, int, int, int, int)}
+     * </ul>
+     *
      * @param version The EGLContext client version to choose. Use 2 for OpenGL ES 2.0
      */
     public void setEGLContextClientVersion(int version) {
@@ -490,6 +509,14 @@
      * @see #RENDERMODE_WHEN_DIRTY
      */
     public void setRenderMode(int renderMode) {
+        switch (renderMode) {
+            case RENDERMODE_WHEN_DIRTY:
+            case RENDERMODE_CONTINUOUSLY:
+                break;
+            default:
+                throw new IllegalArgumentException("renderMode");
+        }
+        mUserRenderMode = renderMode;
         mGLThread.setRenderMode(renderMode);
     }
 
@@ -501,7 +528,7 @@
      * @see #RENDERMODE_WHEN_DIRTY
      */
     public int getRenderMode() {
-        return mGLThread.getRenderMode();
+        return mUserRenderMode;
     }
 
     /**
@@ -582,14 +609,8 @@
             Log.d(TAG, "onAttachedToWindow reattach =" + mDetached);
         }
         if (mDetached && (mRenderer != null)) {
-            int renderMode = RENDERMODE_CONTINUOUSLY;
-            if (mGLThread != null) {
-                renderMode = mGLThread.getRenderMode();
-            }
             mGLThread = new GLThread(mThisWeakRef);
-            if (renderMode != RENDERMODE_CONTINUOUSLY) {
-                mGLThread.setRenderMode(renderMode);
-            }
+            mGLThread.setRenderMode(mUserRenderMode);
             mGLThread.start();
         }
         mDetached = false;
@@ -761,11 +782,15 @@
         void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context);
     }
 
-    private class DefaultContextFactory implements EGLContextFactory {
-        private int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+    private static class DefaultContextFactory implements EGLContextFactory {
+        private final int mEGLContextClientVersion;
+
+        public DefaultContextFactory(int version) {
+            mEGLContextClientVersion = version;
+        }
 
         public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) {
-            int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion,
+            int[] attrib_list = {EGL14.EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion,
                     EGL10.EGL_NONE };
 
             return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT,
@@ -775,9 +800,9 @@
         public void destroyContext(EGL10 egl, EGLDisplay display,
                 EGLContext context) {
             if (!egl.eglDestroyContext(display, context)) {
-                Log.e("DefaultContextFactory", "display:" + display + " context: " + context);
+                Log.e(TAG, "display:" + display + " context: " + context);
                 if (LOG_THREADS) {
-                    Log.i("DefaultContextFactory", "tid=" + Thread.currentThread().getId());
+                    Log.d(TAG, "tid=" + Thread.currentThread().getId());
                 }
                 EglHelper.throwEglException("eglDestroyContex", egl.eglGetError());
             }
@@ -807,8 +832,8 @@
             try {
                 result = egl.eglCreateWindowSurface(display, config, nativeWindow, null);
             } catch (IllegalArgumentException e) {
-                // This exception indicates that the surface flinger surface
-                // is not valid. This can happen if the surface flinger surface has
+                // This exception indicates that the surfaceflinger surface
+                // is not valid. This can happen if the surfaceflinger surface has
                 // been torn down, but the application has not yet been
                 // notified via SurfaceHolder.Callback.surfaceDestroyed.
                 // In theory the application should be notified first,
@@ -844,10 +869,11 @@
         EGLConfig chooseConfig(EGL10 egl, EGLDisplay display);
     }
 
-    private abstract class BaseConfigChooser
+    private static abstract class BaseConfigChooser
             implements EGLConfigChooser {
-        public BaseConfigChooser(int[] configSpec) {
-            mConfigSpec = filterConfigSpec(configSpec);
+
+        public BaseConfigChooser(int[] configSpec, int version) {
+            mConfigSpec = filterConfigSpec(configSpec, version);
         }
 
         public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
@@ -881,8 +907,8 @@
 
         protected int[] mConfigSpec;
 
-        private int[] filterConfigSpec(int[] configSpec) {
-            if (mEGLContextClientVersion != 2) {
+        private int[] filterConfigSpec(int[] configSpec, int version) {
+            if (version != 2) {
                 return configSpec;
             }
             /* We know none of the subclasses define EGL_RENDERABLE_TYPE.
@@ -892,7 +918,7 @@
             int[] newConfigSpec = new int[len + 2];
             System.arraycopy(configSpec, 0, newConfigSpec, 0, len-1);
             newConfigSpec[len-1] = EGL10.EGL_RENDERABLE_TYPE;
-            newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */
+            newConfigSpec[len] = EGL14.EGL_OPENGL_ES2_BIT;
             newConfigSpec[len+1] = EGL10.EGL_NONE;
             return newConfigSpec;
         }
@@ -902,9 +928,9 @@
      * Choose a configuration with exactly the specified r,g,b,a sizes,
      * and at least the specified depth and stencil sizes.
      */
-    private class ComponentSizeChooser extends BaseConfigChooser {
+    private static class ComponentSizeChooser extends BaseConfigChooser {
         public ComponentSizeChooser(int redSize, int greenSize, int blueSize,
-                int alphaSize, int depthSize, int stencilSize) {
+                int alphaSize, int depthSize, int stencilSize, int version) {
             super(new int[] {
                     EGL10.EGL_RED_SIZE, redSize,
                     EGL10.EGL_GREEN_SIZE, greenSize,
@@ -912,7 +938,7 @@
                     EGL10.EGL_ALPHA_SIZE, alphaSize,
                     EGL10.EGL_DEPTH_SIZE, depthSize,
                     EGL10.EGL_STENCIL_SIZE, stencilSize,
-                    EGL10.EGL_NONE});
+                    EGL10.EGL_NONE}, version);
             mValue = new int[1];
             mRedSize = redSize;
             mGreenSize = greenSize;
@@ -920,7 +946,7 @@
             mAlphaSize = alphaSize;
             mDepthSize = depthSize;
             mStencilSize = stencilSize;
-       }
+        }
 
         @Override
         public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
@@ -931,14 +957,10 @@
                 int s = findConfigAttrib(egl, display, config,
                         EGL10.EGL_STENCIL_SIZE, 0);
                 if ((d >= mDepthSize) && (s >= mStencilSize)) {
-                    int r = findConfigAttrib(egl, display, config,
-                            EGL10.EGL_RED_SIZE, 0);
-                    int g = findConfigAttrib(egl, display, config,
-                             EGL10.EGL_GREEN_SIZE, 0);
-                    int b = findConfigAttrib(egl, display, config,
-                              EGL10.EGL_BLUE_SIZE, 0);
-                    int a = findConfigAttrib(egl, display, config,
-                            EGL10.EGL_ALPHA_SIZE, 0);
+                    int r = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0);
+                    int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0);
+                    int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0);
+                    int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0);
                     if ((r == mRedSize) && (g == mGreenSize)
                             && (b == mBlueSize) && (a == mAlphaSize)) {
                         return config;
@@ -965,16 +987,16 @@
         protected int mAlphaSize;
         protected int mDepthSize;
         protected int mStencilSize;
-        }
+    }
 
     /**
      * This class will choose a RGB_888 surface with
      * or without a depth buffer.
      *
      */
-    private class SimpleEGLConfigChooser extends ComponentSizeChooser {
-        public SimpleEGLConfigChooser(boolean withDepthBuffer) {
-            super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0);
+    private static class SimpleEGLConfigChooser extends ComponentSizeChooser {
+        public SimpleEGLConfigChooser(boolean withDepthBuffer, int version) {
+            super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0, version);
         }
     }
 
@@ -991,9 +1013,9 @@
          * Initialize EGL for a given configuration spec.
          * @param configSpec
          */
-        public void start() {
+        public void initialize() {
             if (LOG_EGL) {
-                Log.w("EglHelper", "start() tid=" + Thread.currentThread().getId());
+                Log.d(TAG, "initialize() tid=" + Thread.currentThread().getId());
             }
             /*
              * Get an EGL instance
@@ -1034,7 +1056,7 @@
                 throwEglException("createContext");
             }
             if (LOG_EGL) {
-                Log.w("EglHelper", "createContext " + mEglContext + " tid=" + Thread.currentThread().getId());
+                Log.d(TAG, "createContext " + mEglContext + " tid=" + Thread.currentThread().getId());
             }
 
             mEglSurface = null;
@@ -1048,7 +1070,7 @@
          */
         public boolean createSurface() {
             if (LOG_EGL) {
-                Log.w("EglHelper", "createSurface()  tid=" + Thread.currentThread().getId());
+                Log.d(TAG, "createSurface()  tid=" + Thread.currentThread().getId());
             }
             /*
              * Check preconditions.
@@ -1083,7 +1105,7 @@
             if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
                 int error = mEgl.eglGetError();
                 if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
-                    Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
+                    Log.e(TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
                 }
                 return false;
             }
@@ -1097,8 +1119,9 @@
                  * Could not make the context current, probably because the underlying
                  * SurfaceView surface has been destroyed.
                  */
-                logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
-                return false;
+                logEglErrorAsWarning(TAG, "eglMakeCurrent", mEgl.eglGetError());
+                // we fall-through to "true" here because we do have a
+                // valid EGLSurface at this point.
             }
 
             return true;
@@ -1108,8 +1131,7 @@
          * Create a GL object for the current EGL context.
          * @return
          */
-        GL createGL() {
-
+        public GL createGL() {
             GL gl = mEglContext.getGL();
             GLSurfaceView view = mGLSurfaceViewWeakRef.get();
             if (view != null) {
@@ -1145,7 +1167,7 @@
 
         public void destroySurface() {
             if (LOG_EGL) {
-                Log.w("EglHelper", "destroySurface()  tid=" + Thread.currentThread().getId());
+                Log.d(TAG, "destroySurface()  tid=" + Thread.currentThread().getId());
             }
             destroySurfaceImp();
         }
@@ -1163,9 +1185,9 @@
             }
         }
 
-        public void finish() {
+        public void terminate() {
             if (LOG_EGL) {
-                Log.w("EglHelper", "finish() tid=" + Thread.currentThread().getId());
+                Log.d(TAG, "terminate() tid=" + Thread.currentThread().getId());
             }
             if (mEglContext != null) {
                 GLSurfaceView view = mGLSurfaceViewWeakRef.get();
@@ -1187,7 +1209,7 @@
         public static void throwEglException(String function, int error) {
             String message = formatEglError(function, error);
             if (LOG_THREADS) {
-                Log.e("EglHelper", "throwEglException tid=" + Thread.currentThread().getId() + " "
+                Log.e(TAG, "throwEglException tid=" + Thread.currentThread().getId() + " "
                         + message);
             }
             throw new RuntimeException(message);
@@ -1207,584 +1229,411 @@
         EGLSurface mEglSurface;
         EGLConfig mEglConfig;
         EGLContext mEglContext;
-
     }
 
     /**
      * A generic GL Thread. Takes care of initializing EGL and GL. Delegates
      * to a Renderer instance to do the actual drawing. Can be configured to
      * render continuously or on request.
-     *
-     * All potentially blocking synchronization is done through the
-     * sGLThreadManager object. This avoids multiple-lock ordering issues.
-     *
      */
-    static class GLThread extends Thread {
-        GLThread(WeakReference<GLSurfaceView> glSurfaceViewWeakRef) {
-            super();
+
+    static class GLThread extends HandlerThread {
+        // only accessed from GLThread
+        private GL10 mGLContext;
+        private int mWidth;
+        private int mHeight;
+        private boolean mSizeChanged;
+        // current render mode
+        private int mRenderMode;
+        // the EGLSurface exists but isn't working for some reason
+        private boolean mEglSurfaceIsBad;
+        // we have an EGLContext
+        private boolean mHaveEglContext;
+        // we have an EGLSurface
+        private boolean mHaveEglSurface;
+        // we have a Surface (i.e.: EGLNativeWindowType)
+        private boolean mHasSurface;
+        // activity is paused
+        private boolean mPaused;
+
+        // constants
+        private EglHelper mEglHelper;
+        private Handler mGLHandler;
+        private Choreographer mChoreographer;
+
+        /*
+         * Set once at thread construction time, nulled out when the parent view is garbage
+         * called. This weak reference allows the GLSurfaceView to be garbage collected while
+         * the GLThread is still alive.
+         */
+        private final WeakReference<GLSurfaceView> mGLSurfaceViewWeakRef;
+
+        private final Runnable mExecuteDrawAction = new Runnable() {
+            private int mTraceVsyncCounter = 0;
+            @Override
+            public void run() {
+                if (TRACE_ENABLED) {
+                    Trace.traceCounter(Trace.TRACE_TAG_GRAPHICS,
+                            "GLSurfaceView VSYNC counter", (mTraceVsyncCounter++) & 0xf);
+                }
+                executeDraw();
+            }
+        };
+
+        public GLThread(WeakReference<GLSurfaceView> glSurfaceViewWeakRef) {
+            super("GLThread", android.os.Process.THREAD_PRIORITY_DISPLAY);
+            if (LOG_THREADS) {
+                Log.d(TAG, "*** Starting GLThread ***");
+            }
             mWidth = 0;
             mHeight = 0;
-            mRequestRender = true;
             mRenderMode = RENDERMODE_CONTINUOUSLY;
             mGLSurfaceViewWeakRef = glSurfaceViewWeakRef;
         }
 
+        private void readyToRun() {
+            mChoreographer = Choreographer.getInstance();
+            mEglHelper = new EglHelper(mGLSurfaceViewWeakRef);
+        }
+
+        @Override
+        public void start() {
+            super.start();
+            // getLooper() blocks until the thread is running
+            Looper looper = getLooper();
+            mGLHandler = new Handler(looper);
+            // don't return until the GLThread state has been initialized
+            mGLHandler.runWithScissors(new Runnable() {
+                @Override
+                public void run() {
+                    readyToRun();
+                }
+            }, 0);
+        }
+
         @Override
         public void run() {
-            setName("GLThread " + getId());
-            if (LOG_THREADS) {
-                Log.i("GLThread", "starting tid=" + getId());
-            }
-
             try {
-                guardedRun();
-            } catch (InterruptedException e) {
-                // fall thru and exit normally
+                super.run();
             } finally {
-                sGLThreadManager.threadExiting(this);
+                // by definition the GLThread is not running anymore here
+                stopEglContext();
+                stopEglSurface();
             }
         }
 
-        /*
-         * This private method should only be called inside a
-         * synchronized(sGLThreadManager) block.
-         */
-        private void stopEglSurfaceLocked() {
+        // only call from the GLThread
+        private void stopEglSurface() {
             if (mHaveEglSurface) {
+                if (LOG_SURFACE) {
+                    Log.d(TAG, "releasing EGL surface because paused tid=" + getId());
+                }
                 mHaveEglSurface = false;
                 mEglHelper.destroySurface();
             }
         }
 
-        /*
-         * This private method should only be called inside a
-         * synchronized(sGLThreadManager) block.
-         */
-        private void stopEglContextLocked() {
+        // only call from the GLThread
+        private void stopEglContext() {
             if (mHaveEglContext) {
-                mEglHelper.finish();
+                mEglHelper.terminate();
                 mHaveEglContext = false;
-                sGLThreadManager.releaseEglContextLocked(this);
+                if (LOG_SURFACE) {
+                    Log.d(TAG, "releasing EGL context because paused tid=" + getId());
+                }
             }
         }
-        private void guardedRun() throws InterruptedException {
-            mEglHelper = new EglHelper(mGLSurfaceViewWeakRef);
-            mHaveEglContext = false;
-            mHaveEglSurface = false;
-            try {
-                GL10 gl = null;
-                boolean createEglContext = false;
-                boolean createEglSurface = false;
-                boolean createGlInterface = false;
-                boolean lostEglContext = false;
-                boolean sizeChanged = false;
-                boolean wantRenderNotification = false;
-                boolean doRenderNotification = false;
-                boolean askedToReleaseEglContext = false;
-                int w = 0;
-                int h = 0;
-                Runnable event = null;
 
-                while (true) {
-                    synchronized (sGLThreadManager) {
-                        while (true) {
-                            if (mShouldExit) {
-                                return;
-                            }
+        private void updateState() {
+            final boolean wasAbleToDraw = isAbleToDraw();
+            if (!isReadyToDraw()) {
+                return;
+            }
 
-                            if (! mEventQueue.isEmpty()) {
-                                event = mEventQueue.remove(0);
-                                break;
-                            }
+            if (!mHaveEglSurface || mSizeChanged) {
+                // create EGL context if needed
+                boolean reportSurfaceCreated = false;
+                if (!mHaveEglContext) {
+                    mEglHelper.initialize();
+                    mHaveEglContext = true;
+                    reportSurfaceCreated = true;
+                }
 
-                            // Update the pause state.
-                            boolean pausing = false;
-                            if (mPaused != mRequestPaused) {
-                                pausing = mRequestPaused;
-                                mPaused = mRequestPaused;
-                                sGLThreadManager.notifyAll();
-                                if (LOG_PAUSE_RESUME) {
-                                    Log.i("GLThread", "mPaused is now " + mPaused + " tid=" + getId());
-                                }
-                            }
+                // get the GL interface for the active EGLContext
+                mGLContext = (GL10)mEglHelper.createGL();
 
-                            // Do we need to give up the EGL context?
-                            if (mShouldReleaseEglContext) {
-                                if (LOG_SURFACE) {
-                                    Log.i("GLThread", "releasing EGL context because asked to tid=" + getId());
-                                }
-                                stopEglSurfaceLocked();
-                                stopEglContextLocked();
-                                mShouldReleaseEglContext = false;
-                                askedToReleaseEglContext = true;
-                            }
+                // create EGL Surface
+                mHaveEglSurface = mEglHelper.createSurface();
+                mEglSurfaceIsBad = !mHaveEglSurface;
+                mSizeChanged = false;
 
-                            // Have we lost the EGL context?
-                            if (lostEglContext) {
-                                stopEglSurfaceLocked();
-                                stopEglContextLocked();
-                                lostEglContext = false;
-                            }
-
-                            // When pausing, release the EGL surface:
-                            if (pausing && mHaveEglSurface) {
-                                if (LOG_SURFACE) {
-                                    Log.i("GLThread", "releasing EGL surface because paused tid=" + getId());
-                                }
-                                stopEglSurfaceLocked();
-                            }
-
-                            // When pausing, optionally release the EGL Context:
-                            if (pausing && mHaveEglContext) {
-                                GLSurfaceView view = mGLSurfaceViewWeakRef.get();
-                                boolean preserveEglContextOnPause = view == null ?
-                                        false : view.mPreserveEGLContextOnPause;
-                                if (!preserveEglContextOnPause || sGLThreadManager.shouldReleaseEGLContextWhenPausing()) {
-                                    stopEglContextLocked();
-                                    if (LOG_SURFACE) {
-                                        Log.i("GLThread", "releasing EGL context because paused tid=" + getId());
-                                    }
-                                }
-                            }
-
-                            // When pausing, optionally terminate EGL:
-                            if (pausing) {
-                                if (sGLThreadManager.shouldTerminateEGLWhenPausing()) {
-                                    mEglHelper.finish();
-                                    if (LOG_SURFACE) {
-                                        Log.i("GLThread", "terminating EGL because paused tid=" + getId());
-                                    }
-                                }
-                            }
-
-                            // Have we lost the SurfaceView surface?
-                            if ((! mHasSurface) && (! mWaitingForSurface)) {
-                                if (LOG_SURFACE) {
-                                    Log.i("GLThread", "noticed surfaceView surface lost tid=" + getId());
-                                }
-                                if (mHaveEglSurface) {
-                                    stopEglSurfaceLocked();
-                                }
-                                mWaitingForSurface = true;
-                                mSurfaceIsBad = false;
-                                sGLThreadManager.notifyAll();
-                            }
-
-                            // Have we acquired the surface view surface?
-                            if (mHasSurface && mWaitingForSurface) {
-                                if (LOG_SURFACE) {
-                                    Log.i("GLThread", "noticed surfaceView surface acquired tid=" + getId());
-                                }
-                                mWaitingForSurface = false;
-                                sGLThreadManager.notifyAll();
-                            }
-
-                            if (doRenderNotification) {
-                                if (LOG_SURFACE) {
-                                    Log.i("GLThread", "sending render notification tid=" + getId());
-                                }
-                                wantRenderNotification = false;
-                                doRenderNotification = false;
-                                mRenderComplete = true;
-                                sGLThreadManager.notifyAll();
-                            }
-
-                            // Ready to draw?
-                            if (readyToDraw()) {
-
-                                // If we don't have an EGL context, try to acquire one.
-                                if (! mHaveEglContext) {
-                                    if (askedToReleaseEglContext) {
-                                        askedToReleaseEglContext = false;
-                                    } else if (sGLThreadManager.tryAcquireEglContextLocked(this)) {
-                                        try {
-                                            mEglHelper.start();
-                                        } catch (RuntimeException t) {
-                                            sGLThreadManager.releaseEglContextLocked(this);
-                                            throw t;
-                                        }
-                                        mHaveEglContext = true;
-                                        createEglContext = true;
-
-                                        sGLThreadManager.notifyAll();
-                                    }
-                                }
-
-                                if (mHaveEglContext && !mHaveEglSurface) {
-                                    mHaveEglSurface = true;
-                                    createEglSurface = true;
-                                    createGlInterface = true;
-                                    sizeChanged = true;
-                                }
-
-                                if (mHaveEglSurface) {
-                                    if (mSizeChanged) {
-                                        sizeChanged = true;
-                                        w = mWidth;
-                                        h = mHeight;
-                                        wantRenderNotification = true;
-                                        if (LOG_SURFACE) {
-                                            Log.i("GLThread",
-                                                    "noticing that we want render notification tid="
-                                                    + getId());
-                                        }
-
-                                        // Destroy and recreate the EGL surface.
-                                        createEglSurface = true;
-
-                                        mSizeChanged = false;
-                                    }
-                                    mRequestRender = false;
-                                    sGLThreadManager.notifyAll();
-                                    break;
-                                }
-                            }
-
-                            // By design, this is the only place in a GLThread thread where we wait().
-                            if (LOG_THREADS) {
-                                Log.i("GLThread", "waiting tid=" + getId()
-                                    + " mHaveEglContext: " + mHaveEglContext
-                                    + " mHaveEglSurface: " + mHaveEglSurface
-                                    + " mFinishedCreatingEglSurface: " + mFinishedCreatingEglSurface
-                                    + " mPaused: " + mPaused
-                                    + " mHasSurface: " + mHasSurface
-                                    + " mSurfaceIsBad: " + mSurfaceIsBad
-                                    + " mWaitingForSurface: " + mWaitingForSurface
-                                    + " mWidth: " + mWidth
-                                    + " mHeight: " + mHeight
-                                    + " mRequestRender: " + mRequestRender
-                                    + " mRenderMode: " + mRenderMode);
-                            }
-                            sGLThreadManager.wait();
-                        }
-                    } // end of synchronized(sGLThreadManager)
-
-                    if (event != null) {
-                        event.run();
-                        event = null;
-                        continue;
-                    }
-
-                    if (createEglSurface) {
-                        if (LOG_SURFACE) {
-                            Log.w("GLThread", "egl createSurface");
-                        }
-                        if (mEglHelper.createSurface()) {
-                            synchronized(sGLThreadManager) {
-                                mFinishedCreatingEglSurface = true;
-                                sGLThreadManager.notifyAll();
-                            }
-                        } else {
-                            synchronized(sGLThreadManager) {
-                                mFinishedCreatingEglSurface = true;
-                                mSurfaceIsBad = true;
-                                sGLThreadManager.notifyAll();
-                            }
-                            continue;
-                        }
-                        createEglSurface = false;
-                    }
-
-                    if (createGlInterface) {
-                        gl = (GL10) mEglHelper.createGL();
-
-                        sGLThreadManager.checkGLDriver(gl);
-                        createGlInterface = false;
-                    }
-
-                    if (createEglContext) {
+                // notify use of surface size change
+                GLSurfaceView view = mGLSurfaceViewWeakRef.get();
+                if (view != null) {
+                    if (reportSurfaceCreated) {
                         if (LOG_RENDERER) {
-                            Log.w("GLThread", "onSurfaceCreated");
+                            Log.d(TAG, "onSurfaceCreated");
                         }
-                        GLSurfaceView view = mGLSurfaceViewWeakRef.get();
-                        if (view != null) {
-                            view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
-                        }
-                        createEglContext = false;
+                        view.mRenderer.onSurfaceCreated(mGLContext, mEglHelper.mEglConfig);
                     }
 
-                    if (sizeChanged) {
-                        if (LOG_RENDERER) {
-                            Log.w("GLThread", "onSurfaceChanged(" + w + ", " + h + ")");
-                        }
-                        GLSurfaceView view = mGLSurfaceViewWeakRef.get();
-                        if (view != null) {
-                            view.mRenderer.onSurfaceChanged(gl, w, h);
-                        }
-                        sizeChanged = false;
+                    if (LOG_RENDERER) {
+                        Log.d(TAG, "onSurfaceChanged(" + mWidth + ", " + mHeight + ")");
                     }
+                    view.mRenderer.onSurfaceChanged(mGLContext, mWidth, mHeight);
+                }
+            }
 
-                    if (LOG_RENDERER_DRAW_FRAME) {
-                        Log.w("GLThread", "onDrawFrame tid=" + getId());
-                    }
-                    {
-                        GLSurfaceView view = mGLSurfaceViewWeakRef.get();
-                        if (view != null) {
-                            view.mRenderer.onDrawFrame(gl);
-                        }
-                    }
+            // see if we should kick the rendering loop
+            if (!wasAbleToDraw && isAbleToDraw()) {
+                // we're now able to draw
+                if (mRenderMode == RENDERMODE_CONTINUOUSLY) {
+                    requestRender();
+                }
+            }
+
+            // By design, this is the only place in a GLThread thread where we wait().
+            if (LOG_THREADS) {
+                Log.d(TAG, "waiting tid=" + getId()
+                        + " mHaveEglContext: " + mHaveEglContext
+                        + " mHaveEglSurface: " + mHaveEglSurface
+                        + " mPaused: " + mPaused
+                        + " mHasSurface: " + mHasSurface
+                        + " mSurfaceIsBad: " + mEglSurfaceIsBad
+                        + " mWidth: " + mWidth
+                        + " mHeight: " + mHeight
+                        + " mRenderMode: " + mRenderMode);
+            }
+        }
+
+        private void executeDraw() {
+            if (TRACE_ENABLED) {
+                Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "executeDraw");
+            }
+
+            if (isAbleToDraw()) {
+                if (mRenderMode == RENDERMODE_CONTINUOUSLY) {
+                    requestRender();
+                }
+
+                if (LOG_RENDERER_DRAW_FRAME) {
+                    Log.d(TAG, "onDrawFrame tid=" + getId());
+                }
+
+                GLSurfaceView view = mGLSurfaceViewWeakRef.get();
+                if (view != null) {
+                    view.mRenderer.onDrawFrame(mGLContext);
                     int swapError = mEglHelper.swap();
                     switch (swapError) {
                         case EGL10.EGL_SUCCESS:
                             break;
                         case EGL11.EGL_CONTEXT_LOST:
                             if (LOG_SURFACE) {
-                                Log.i("GLThread", "egl context lost tid=" + getId());
+                                Log.d(TAG, "egl context lost tid=" + getId());
                             }
-                            lostEglContext = true;
+                            stopEglSurface();
+                            stopEglContext();
                             break;
                         default:
                             // Other errors typically mean that the current surface is bad,
                             // probably because the SurfaceView surface has been destroyed,
                             // but we haven't been notified yet.
                             // Log the error to help developers understand why rendering stopped.
-                            EglHelper.logEglErrorAsWarning("GLThread", "eglSwapBuffers", swapError);
-
-                            synchronized(sGLThreadManager) {
-                                mSurfaceIsBad = true;
-                                sGLThreadManager.notifyAll();
-                            }
+                            EglHelper.logEglErrorAsWarning(TAG, "eglSwapBuffers", swapError);
+                            mEglSurfaceIsBad = true;
                             break;
                     }
-
-                    if (wantRenderNotification) {
-                        doRenderNotification = true;
-                    }
                 }
+            }
 
-            } finally {
-                /*
-                 * clean-up everything...
-                 */
-                synchronized (sGLThreadManager) {
-                    stopEglSurfaceLocked();
-                    stopEglContextLocked();
+            if (TRACE_ENABLED) {
+                Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
+            }
+        }
+
+        private boolean isAbleToDraw() {
+            return mHaveEglContext && mHaveEglSurface && isReadyToDraw();
+        }
+
+        private boolean isReadyToDraw() {
+            return (!mPaused) && mHasSurface && (!mEglSurfaceIsBad)
+                && (mWidth > 0) && (mHeight > 0);
+        }
+
+        private boolean isEglContextReleasedWhenPausing() {
+            GLSurfaceView view = mGLSurfaceViewWeakRef.get();
+            return (view != null) ? !view.mPreserveEGLContextOnPause : false;
+        }
+
+        public void queueEvent(Runnable r) {
+            if (r == null) {
+                throw new IllegalArgumentException("Runnable r must not be null");
+            }
+            mGLHandler.post(r);
+        }
+
+        /*
+         * the call-backs below all run on the GLThread and implement state
+         * changes of the GLSurfaceView and Activity life cycle.
+         */
+
+        private void doSurfaceCreated() {
+            mHasSurface = true;
+            updateState();
+        }
+
+        private void doSurfaceDestroyed() {
+            if (mHasSurface) {
+                if (LOG_SURFACE) {
+                    Log.d(TAG, "noticed surfaceView surface lost tid=" + getId());
+                }
+                stopEglSurface();
+            }
+            mHasSurface = false;
+        }
+
+        private void doPause() {
+            if (mPaused == false) {
+                mPaused = true;
+                stopEglSurface();
+                // When pausing, optionally release the EGL Context:
+                if (mHaveEglContext && isEglContextReleasedWhenPausing()) {
+                    stopEglContext();
                 }
             }
         }
 
-        public boolean ableToDraw() {
-            return mHaveEglContext && mHaveEglSurface && readyToDraw();
-        }
-
-        private boolean readyToDraw() {
-            return (!mPaused) && mHasSurface && (!mSurfaceIsBad)
-                && (mWidth > 0) && (mHeight > 0)
-                && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY));
-        }
-
-        public void setRenderMode(int renderMode) {
-            if ( !((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY)) ) {
-                throw new IllegalArgumentException("renderMode");
-            }
-            synchronized(sGLThreadManager) {
-                mRenderMode = renderMode;
-                sGLThreadManager.notifyAll();
+        private void doResume() {
+            mPaused = false;
+            updateState();
+            if (mRenderMode == RENDERMODE_WHEN_DIRTY) {
+                executeDraw();
             }
         }
 
-        public int getRenderMode() {
-            synchronized(sGLThreadManager) {
-                return mRenderMode;
-            }
+        private void doWindowResize(final int width, final int height) {
+            // we were not drawing yet. Update the window size and
+            // state and attempt to draw a frame.
+            mSizeChanged = (mWidth != width || mHeight != height);
+            mWidth = width;
+            mHeight = height;
+            updateState();
+            // we always (attempt to) draw a frame before returning
+            executeDraw();
         }
 
-        public void requestRender() {
-            synchronized(sGLThreadManager) {
-                mRequestRender = true;
-                sGLThreadManager.notifyAll();
-            }
+        private void doSetRenderMode(final int renderMode) {
+            mRenderMode = renderMode;
+            requestRender();
         }
 
+        /*
+         * the call-backs below run on the main UI thread, they just
+         * wait while executing work on the GLThread.
+         */
+
         public void surfaceCreated() {
-            synchronized(sGLThreadManager) {
-                if (LOG_THREADS) {
-                    Log.i("GLThread", "surfaceCreated tid=" + getId());
+            mGLHandler.runWithScissors(new Runnable() {
+                @Override
+                public void run() {
+                    doSurfaceCreated();
                 }
-                mHasSurface = true;
-                mFinishedCreatingEglSurface = false;
-                sGLThreadManager.notifyAll();
-                while (mWaitingForSurface
-                       && !mFinishedCreatingEglSurface
-                       && !mExited) {
-                    try {
-                        sGLThreadManager.wait();
-                    } catch (InterruptedException e) {
-                        Thread.currentThread().interrupt();
-                    }
-                }
-            }
+            }, 0);
         }
 
         public void surfaceDestroyed() {
-            synchronized(sGLThreadManager) {
-                if (LOG_THREADS) {
-                    Log.i("GLThread", "surfaceDestroyed tid=" + getId());
+            mGLHandler.runWithScissors(new Runnable() {
+                @Override
+                public void run() {
+                    doSurfaceDestroyed();
                 }
-                mHasSurface = false;
-                sGLThreadManager.notifyAll();
-                while((!mWaitingForSurface) && (!mExited)) {
-                    try {
-                        sGLThreadManager.wait();
-                    } catch (InterruptedException e) {
-                        Thread.currentThread().interrupt();
-                    }
-                }
-            }
+            }, 0);
         }
 
         public void onPause() {
-            synchronized (sGLThreadManager) {
-                if (LOG_PAUSE_RESUME) {
-                    Log.i("GLThread", "onPause tid=" + getId());
+            mGLHandler.runWithScissors(new Runnable() {
+                @Override
+                public void run() {
+                    doPause();
                 }
-                mRequestPaused = true;
-                sGLThreadManager.notifyAll();
-                while ((! mExited) && (! mPaused)) {
-                    if (LOG_PAUSE_RESUME) {
-                        Log.i("Main thread", "onPause waiting for mPaused.");
-                    }
-                    try {
-                        sGLThreadManager.wait();
-                    } catch (InterruptedException ex) {
-                        Thread.currentThread().interrupt();
-                    }
-                }
-            }
+            }, 0);
         }
 
         public void onResume() {
-            synchronized (sGLThreadManager) {
-                if (LOG_PAUSE_RESUME) {
-                    Log.i("GLThread", "onResume tid=" + getId());
+            mGLHandler.runWithScissors(new Runnable() {
+                @Override
+                public void run() {
+                    doResume();
                 }
-                mRequestPaused = false;
-                mRequestRender = true;
-                mRenderComplete = false;
-                sGLThreadManager.notifyAll();
-                while ((! mExited) && mPaused && (!mRenderComplete)) {
-                    if (LOG_PAUSE_RESUME) {
-                        Log.i("Main thread", "onResume waiting for !mPaused.");
-                    }
-                    try {
-                        sGLThreadManager.wait();
-                    } catch (InterruptedException ex) {
-                        Thread.currentThread().interrupt();
-                    }
-                }
-            }
+            }, 0);
         }
 
         public void onWindowResize(int w, int h) {
-            synchronized (sGLThreadManager) {
-                mWidth = w;
-                mHeight = h;
-                mSizeChanged = true;
-                mRequestRender = true;
-                mRenderComplete = false;
-                sGLThreadManager.notifyAll();
-
-                // Wait for thread to react to resize and render a frame
-                while (! mExited && !mPaused && !mRenderComplete
-                        && ableToDraw()) {
-                    if (LOG_SURFACE) {
-                        Log.i("Main thread", "onWindowResize waiting for render complete from tid=" + getId());
-                    }
-                    try {
-                        sGLThreadManager.wait();
-                    } catch (InterruptedException ex) {
-                        Thread.currentThread().interrupt();
-                    }
+            final int width = w;
+            final int height = h;
+            mGLHandler.runWithScissors(new Runnable() {
+                @Override
+                public void run() {
+                    doWindowResize(width, height);
                 }
+            }, 0);
+        }
+
+        /*
+         * the methods below can be called from any thread
+         */
+
+        public void requestRender() {
+            if (mRenderMode == RENDERMODE_CONTINUOUSLY) {
+                mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION,
+                        mExecuteDrawAction, null);
+            } else {
+                /*
+                 * in RENDERMODE_WHEN_DIRTY we schedule the draw callback
+                 * immediately because the developer is manager her
+                 * timing loop manually -- in particular she could be
+                 * using the Choreographer already.
+                 */
+                mGLHandler.post(mExecuteDrawAction);
             }
         }
 
+        public void setRenderMode(final int renderMode) {
+            mGLHandler.runWithScissors(new Runnable() {
+                @Override
+                public void run() {
+                    doSetRenderMode(renderMode);
+                }
+            }, 0);
+        }
+
         public void requestExitAndWait() {
-            // don't call this from GLThread thread or it is a guaranteed
-            // deadlock!
-            synchronized(sGLThreadManager) {
-                mShouldExit = true;
-                sGLThreadManager.notifyAll();
-                while (! mExited) {
-                    try {
-                        sGLThreadManager.wait();
-                    } catch (InterruptedException ex) {
-                        Thread.currentThread().interrupt();
-                    }
-                }
+            getLooper().quit();
+            try {
+                this.join();
+            } catch (InterruptedException e) {
             }
         }
-
-        public void requestReleaseEglContextLocked() {
-            mShouldReleaseEglContext = true;
-            sGLThreadManager.notifyAll();
-        }
-
-        /**
-         * Queue an "event" to be run on the GL rendering thread.
-         * @param r the runnable to be run on the GL rendering thread.
-         */
-        public void queueEvent(Runnable r) {
-            if (r == null) {
-                throw new IllegalArgumentException("r must not be null");
-            }
-            synchronized(sGLThreadManager) {
-                mEventQueue.add(r);
-                sGLThreadManager.notifyAll();
-            }
-        }
-
-        // Once the thread is started, all accesses to the following member
-        // variables are protected by the sGLThreadManager monitor
-        private boolean mShouldExit;
-        private boolean mExited;
-        private boolean mRequestPaused;
-        private boolean mPaused;
-        private boolean mHasSurface;
-        private boolean mSurfaceIsBad;
-        private boolean mWaitingForSurface;
-        private boolean mHaveEglContext;
-        private boolean mHaveEglSurface;
-        private boolean mFinishedCreatingEglSurface;
-        private boolean mShouldReleaseEglContext;
-        private int mWidth;
-        private int mHeight;
-        private int mRenderMode;
-        private boolean mRequestRender;
-        private boolean mRenderComplete;
-        private ArrayList<Runnable> mEventQueue = new ArrayList<Runnable>();
-        private boolean mSizeChanged = true;
-
-        // End of member variables protected by the sGLThreadManager monitor.
-
-        private EglHelper mEglHelper;
-
-        /**
-         * Set once at thread construction time, nulled out when the parent view is garbage
-         * called. This weak reference allows the GLSurfaceView to be garbage collected while
-         * the GLThread is still alive.
-         */
-        private WeakReference<GLSurfaceView> mGLSurfaceViewWeakRef;
-
-    }
+    } // class GLThread
 
     static class LogWriter extends Writer {
-
-        @Override public void close() {
+        @Override
+        public void close() {
             flushBuilder();
         }
 
-        @Override public void flush() {
+        @Override
+        public void flush() {
             flushBuilder();
         }
 
-        @Override public void write(char[] buf, int offset, int count) {
-            for(int i = 0; i < count; i++) {
+        @Override
+        public void write(char[] buf, int offset, int count) {
+            for (int i = 0; i < count; i++) {
                 char c = buf[offset + i];
-                if ( c == '\n') {
+                if (c == '\n') {
                     flushBuilder();
-                }
-                else {
+                } else {
                     mBuilder.append(c);
                 }
             }
@@ -1792,7 +1641,7 @@
 
         private void flushBuilder() {
             if (mBuilder.length() > 0) {
-                Log.v("GLSurfaceView", mBuilder.toString());
+                Log.v(TAG, mBuilder.toString());
                 mBuilder.delete(0, mBuilder.length());
             }
         }
@@ -1800,141 +1649,10 @@
         private StringBuilder mBuilder = new StringBuilder();
     }
 
-
     private void checkRenderThreadState() {
         if (mGLThread != null) {
             throw new IllegalStateException(
                     "setRenderer has already been called for this instance.");
         }
     }
-
-    private static class GLThreadManager {
-        private static String TAG = "GLThreadManager";
-
-        public synchronized void threadExiting(GLThread thread) {
-            if (LOG_THREADS) {
-                Log.i("GLThread", "exiting tid=" +  thread.getId());
-            }
-            thread.mExited = true;
-            if (mEglOwner == thread) {
-                mEglOwner = null;
-            }
-            notifyAll();
-        }
-
-        /*
-         * Tries once to acquire the right to use an EGL
-         * context. Does not block. Requires that we are already
-         * in the sGLThreadManager monitor when this is called.
-         *
-         * @return true if the right to use an EGL context was acquired.
-         */
-        public boolean tryAcquireEglContextLocked(GLThread thread) {
-            if (mEglOwner == thread || mEglOwner == null) {
-                mEglOwner = thread;
-                notifyAll();
-                return true;
-            }
-            checkGLESVersion();
-            if (mMultipleGLESContextsAllowed) {
-                return true;
-            }
-            // Notify the owning thread that it should release the context.
-            // TODO: implement a fairness policy. Currently
-            // if the owning thread is drawing continuously it will just
-            // reacquire the EGL context.
-            if (mEglOwner != null) {
-                mEglOwner.requestReleaseEglContextLocked();
-            }
-            return false;
-        }
-
-        /*
-         * Releases the EGL context. Requires that we are already in the
-         * sGLThreadManager monitor when this is called.
-         */
-        public void releaseEglContextLocked(GLThread thread) {
-            if (mEglOwner == thread) {
-                mEglOwner = null;
-            }
-            notifyAll();
-        }
-
-        public synchronized boolean shouldReleaseEGLContextWhenPausing() {
-            // Release the EGL context when pausing even if
-            // the hardware supports multiple EGL contexts.
-            // Otherwise the device could run out of EGL contexts.
-            return mLimitedGLESContexts;
-        }
-
-        public synchronized boolean shouldTerminateEGLWhenPausing() {
-            checkGLESVersion();
-            return !mMultipleGLESContextsAllowed;
-        }
-
-        public synchronized void checkGLDriver(GL10 gl) {
-            if (! mGLESDriverCheckComplete) {
-                checkGLESVersion();
-                String renderer = gl.glGetString(GL10.GL_RENDERER);
-                if (mGLESVersion < kGLES_20) {
-                    mMultipleGLESContextsAllowed =
-                        ! renderer.startsWith(kMSM7K_RENDERER_PREFIX);
-                    notifyAll();
-                }
-                mLimitedGLESContexts = !mMultipleGLESContextsAllowed;
-                if (LOG_SURFACE) {
-                    Log.w(TAG, "checkGLDriver renderer = \"" + renderer + "\" multipleContextsAllowed = "
-                        + mMultipleGLESContextsAllowed
-                        + " mLimitedGLESContexts = " + mLimitedGLESContexts);
-                }
-                mGLESDriverCheckComplete = true;
-            }
-        }
-
-        private void checkGLESVersion() {
-            if (! mGLESVersionCheckComplete) {
-                mGLESVersion = SystemProperties.getInt(
-                        "ro.opengles.version",
-                        ConfigurationInfo.GL_ES_VERSION_UNDEFINED);
-                if (mGLESVersion >= kGLES_20) {
-                    mMultipleGLESContextsAllowed = true;
-                }
-                if (LOG_SURFACE) {
-                    Log.w(TAG, "checkGLESVersion mGLESVersion =" +
-                            " " + mGLESVersion + " mMultipleGLESContextsAllowed = " + mMultipleGLESContextsAllowed);
-                }
-                mGLESVersionCheckComplete = true;
-            }
-        }
-
-        /**
-         * This check was required for some pre-Android-3.0 hardware. Android 3.0 provides
-         * support for hardware-accelerated views, therefore multiple EGL contexts are
-         * supported on all Android 3.0+ EGL drivers.
-         */
-        private boolean mGLESVersionCheckComplete;
-        private int mGLESVersion;
-        private boolean mGLESDriverCheckComplete;
-        private boolean mMultipleGLESContextsAllowed;
-        private boolean mLimitedGLESContexts;
-        private static final int kGLES_20 = 0x20000;
-        private static final String kMSM7K_RENDERER_PREFIX =
-            "Q3Dimension MSM7500 ";
-        private GLThread mEglOwner;
-    }
-
-    private static final GLThreadManager sGLThreadManager = new GLThreadManager();
-
-    private final WeakReference<GLSurfaceView> mThisWeakRef =
-            new WeakReference<GLSurfaceView>(this);
-    private GLThread mGLThread;
-    private Renderer mRenderer;
-    private boolean mDetached;
-    private EGLConfigChooser mEGLConfigChooser;
-    private EGLContextFactory mEGLContextFactory;
-    private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory;
-    private GLWrapper mGLWrapper;
-    private int mDebugFlags;
-    private int mEGLContextClientVersion;
-    private boolean mPreserveEGLContextOnPause;
 }
diff --git a/packages/DefaultContainerService/jni/Android.mk b/packages/DefaultContainerService/jni/Android.mk
index 79ff451..ef4f699 100644
--- a/packages/DefaultContainerService/jni/Android.mk
+++ b/packages/DefaultContainerService/jni/Android.mk
@@ -28,7 +28,8 @@
 
 LOCAL_SHARED_LIBRARIES := \
     libnativehelper \
-    libutils
+    libutils \
+    liblog
 
 LOCAL_STATIC_LIBRARIES := \
     libdiskusage
@@ -36,4 +37,4 @@
 LOCAL_MODULE := libdefcontainer_jni
 LOCAL_MODULE_TAGS := optional
 
-include $(BUILD_SHARED_LIBRARY)
\ No newline at end of file
+include $(BUILD_SHARED_LIBRARY)
diff --git a/services/common_time/Android.mk b/services/common_time/Android.mk
index 0606ab4..75eb528 100644
--- a/services/common_time/Android.mk
+++ b/services/common_time/Android.mk
@@ -27,7 +27,8 @@
 LOCAL_SHARED_LIBRARIES := \
     libbinder \
     libcommon_time_client \
-    libutils
+    libutils \
+    liblog
 
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE := common_time
diff --git a/services/input/Android.mk b/services/input/Android.mk
index 159800f..5d913f3 100644
--- a/services/input/Android.mk
+++ b/services/input/Android.mk
@@ -29,6 +29,7 @@
 
 LOCAL_SHARED_LIBRARIES := \
     libcutils \
+    liblog \
     libandroidfw \
     libutils \
     libhardware \
diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp
index 0773afb..f4e1cec 100644
--- a/services/input/EventHub.cpp
+++ b/services/input/EventHub.cpp
@@ -40,7 +40,6 @@
 #include <androidfw/KeyCharacterMap.h>
 #include <androidfw/VirtualKeyMap.h>
 
-#include <sha1.h>
 #include <string.h>
 #include <stdint.h>
 #include <dirent.h>
@@ -49,6 +48,7 @@
 #include <sys/epoll.h>
 #include <sys/ioctl.h>
 #include <sys/limits.h>
+#include <sys/sha1.h>
 
 /* this macro is used to tell if "bit" is set in "array"
  * it selects a byte from the array, and does a boolean AND
@@ -162,7 +162,8 @@
         next(NULL),
         fd(fd), id(id), path(path), identifier(identifier),
         classes(0), configuration(NULL), virtualKeyMap(NULL),
-        ffEffectPlaying(false), ffEffectId(-1) {
+        ffEffectPlaying(false), ffEffectId(-1),
+        timestampOverrideSec(0), timestampOverrideUsec(0) {
     memset(keyBitmask, 0, sizeof(keyBitmask));
     memset(absBitmask, 0, sizeof(absBitmask));
     memset(relBitmask, 0, sizeof(relBitmask));
@@ -766,12 +767,37 @@
 
                     size_t count = size_t(readSize) / sizeof(struct input_event);
                     for (size_t i = 0; i < count; i++) {
-                        const struct input_event& iev = readBuffer[i];
-                        ALOGV("%s got: t0=%d, t1=%d, type=%d, code=%d, value=%d",
+                        struct input_event& iev = readBuffer[i];
+                        ALOGV("%s got: time=%d.%06d, type=%d, code=%d, value=%d",
                                 device->path.string(),
                                 (int) iev.time.tv_sec, (int) iev.time.tv_usec,
                                 iev.type, iev.code, iev.value);
 
+                        // Some input devices may have a better concept of the time
+                        // when an input event was actually generated than the kernel
+                        // which simply timestamps all events on entry to evdev.
+                        // This is a custom Android extension of the input protocol
+                        // mainly intended for use with uinput based device drivers.
+                        if (iev.type == EV_MSC) {
+                            if (iev.code == MSC_ANDROID_TIME_SEC) {
+                                device->timestampOverrideSec = iev.value;
+                                continue;
+                            } else if (iev.code == MSC_ANDROID_TIME_USEC) {
+                                device->timestampOverrideUsec = iev.value;
+                                continue;
+                            }
+                        }
+                        if (device->timestampOverrideSec || device->timestampOverrideUsec) {
+                            iev.time.tv_sec = device->timestampOverrideSec;
+                            iev.time.tv_usec = device->timestampOverrideUsec;
+                            if (iev.type == EV_SYN && iev.code == SYN_REPORT) {
+                                device->timestampOverrideSec = 0;
+                                device->timestampOverrideUsec = 0;
+                            }
+                            ALOGV("applied override time %d.%06d",
+                                    int(iev.time.tv_sec), int(iev.time.tv_usec));
+                        }
+
 #ifdef HAVE_POSIX_CLOCKS
                         // Use the time specified in the event instead of the current time
                         // so that downstream code can get more accurate estimates of
@@ -829,8 +855,8 @@
                         event->code = iev.code;
                         event->value = iev.value;
                         event += 1;
+                        capacity -= 1;
                     }
-                    capacity -= count;
                     if (capacity == 0) {
                         // The result buffer is full.  Reset the pending event index
                         // so we will try to read the device again on the next iteration.
diff --git a/services/input/EventHub.h b/services/input/EventHub.h
index afc12ef..c93fc7a 100644
--- a/services/input/EventHub.h
+++ b/services/input/EventHub.h
@@ -42,6 +42,20 @@
 #define BTN_FIRST 0x100  // first button code
 #define BTN_LAST 0x15f   // last button code
 
+/*
+ * These constants are used privately in Android to pass raw timestamps
+ * through evdev from uinput device drivers because there is currently no
+ * other way to transfer this information.  The evdev driver automatically
+ * timestamps all input events with the time they were posted and clobbers
+ * whatever information was passed in.
+ *
+ * For the purposes of this hack, the timestamp is specified in the
+ * CLOCK_MONOTONIC timebase and is split into two EV_MSC events specifying
+ * seconds and microseconds.
+ */
+#define MSC_ANDROID_TIME_SEC 0x6
+#define MSC_ANDROID_TIME_USEC 0x7
+
 namespace android {
 
 enum {
@@ -329,6 +343,9 @@
         bool ffEffectPlaying;
         int16_t ffEffectId; // initially -1
 
+        int32_t timestampOverrideSec;
+        int32_t timestampOverrideUsec;
+
         Device(int fd, int32_t id, const String8& path, const InputDeviceIdentifier& identifier);
         ~Device();
 
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index 602afd4..ab38ed20 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -2701,6 +2701,12 @@
                 mPointerYZoomScale);
         dump.appendFormat(INDENT4 "MaxSwipeWidth: %f\n",
                 mPointerGestureMaxSwipeWidth);
+    } else if (mDeviceMode == DEVICE_MODE_NAVIGATION) {
+        dump.appendFormat(INDENT3 "Navigation Gesture Detector:\n");
+        dump.appendFormat(INDENT4 "AssistStartY: %0.3f\n",
+                mNavigationAssistStartY);
+        dump.appendFormat(INDENT4 "AssistEndY: %0.3f\n",
+                mNavigationAssistEndY);
     }
 }
 
@@ -2895,7 +2901,7 @@
         }
     } else if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_NAVIGATION) {
         mSource = AINPUT_SOURCE_TOUCH_NAVIGATION;
-        mDeviceMode = DEVICE_MODE_UNSCALED;
+        mDeviceMode = DEVICE_MODE_NAVIGATION;
     } else {
         mSource = AINPUT_SOURCE_TOUCHPAD;
         mDeviceMode = DEVICE_MODE_UNSCALED;
@@ -3243,8 +3249,8 @@
             break;
         }
 
-        // Compute pointer gesture detection parameters.
         if (mDeviceMode == DEVICE_MODE_POINTER) {
+            // Compute pointer gesture detection parameters.
             float rawDiagonal = hypotf(rawWidth, rawHeight);
             float displayDiagonal = hypotf(mSurfaceWidth, mSurfaceHeight);
 
@@ -3269,10 +3275,14 @@
             // translated into freeform gestures.
             mPointerGestureMaxSwipeWidth =
                     mConfig.pointerGestureSwipeMaxWidthRatio * rawDiagonal;
-        }
 
-        // Abort current pointer usages because the state has changed.
-        abortPointerUsage(when, 0 /*policyFlags*/);
+            // Abort current pointer usages because the state has changed.
+            abortPointerUsage(when, 0 /*policyFlags*/);
+        } else if (mDeviceMode == DEVICE_MODE_NAVIGATION) {
+            // Compute navigation parameters.
+            mNavigationAssistStartY = mSurfaceHeight * 0.9f;
+            mNavigationAssistEndY = mSurfaceHeight * 0.5f;
+        }
 
         // Inform the dispatcher about the changes.
         *outResetNeeded = true;
@@ -3611,6 +3621,7 @@
 
     mPointerGesture.reset();
     mPointerSimple.reset();
+    mNavigation.reset();
 
     if (mPointerController != NULL) {
         mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
@@ -3761,6 +3772,8 @@
                 mPointerController->setSpots(mCurrentCookedPointerData.pointerCoords,
                         mCurrentCookedPointerData.idToIndex,
                         mCurrentCookedPointerData.touchingIdBits);
+            } else if (mDeviceMode == DEVICE_MODE_NAVIGATION) {
+                dispatchNavigationAssist(when, policyFlags);
             }
 
             dispatchHoverExit(when, policyFlags);
@@ -5482,6 +5495,44 @@
     dispatchPointerSimple(when, policyFlags, false, false);
 }
 
+void TouchInputMapper::dispatchNavigationAssist(nsecs_t when, uint32_t policyFlags) {
+    if (mCurrentCookedPointerData.touchingIdBits.count() == 1) {
+        if (mLastCookedPointerData.touchingIdBits.isEmpty()) {
+            // First pointer down.
+            uint32_t id = mCurrentCookedPointerData.touchingIdBits.firstMarkedBit();
+            const PointerCoords& coords = mCurrentCookedPointerData.pointerCoordsForId(id);
+            if (coords.getY() >= mNavigationAssistStartY) {
+                // Start tracking the possible assist swipe.
+                mNavigation.activeAssistId = id;
+                return;
+            }
+        } else if (mNavigation.activeAssistId >= 0
+                && mCurrentCookedPointerData.touchingIdBits.hasBit(mNavigation.activeAssistId)) {
+            const PointerCoords& coords = mCurrentCookedPointerData.pointerCoordsForId(
+                    mNavigation.activeAssistId);
+            if (coords.getY() > mNavigationAssistEndY) {
+                // Swipe is still in progress.
+                return;
+            }
+
+            // Detected assist swipe.
+            int32_t metaState = mContext->getGlobalMetaState();
+            NotifyKeyArgs downArgs(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD,
+                    policyFlags | POLICY_FLAG_VIRTUAL,
+                    AKEY_EVENT_ACTION_DOWN, 0, AKEYCODE_ASSIST, 0, metaState, when);
+            getListener()->notifyKey(&downArgs);
+
+            NotifyKeyArgs upArgs(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD,
+                    policyFlags | POLICY_FLAG_VIRTUAL,
+                    AKEY_EVENT_ACTION_UP, 0, AKEYCODE_ASSIST, 0, metaState, when);
+            getListener()->notifyKey(&upArgs);
+        }
+    }
+
+    // Cancel the assist swipe.
+    mNavigation.activeAssistId = -1;
+}
+
 void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source,
         int32_t action, int32_t flags, int32_t metaState, int32_t buttonState, int32_t edgeFlags,
         const PointerProperties* properties, const PointerCoords* coords,
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
index 8a52c06..312f19b 100644
--- a/services/input/InputReader.h
+++ b/services/input/InputReader.h
@@ -791,6 +791,10 @@
     void clear();
     void copyFrom(const CookedPointerData& other);
 
+    inline const PointerCoords& pointerCoordsForId(uint32_t id) const {
+        return pointerCoords[idToIndex[id]];
+    }
+
     inline bool isHovering(uint32_t pointerIndex) {
         return hoveringIdBits.hasBit(pointerProperties[pointerIndex].id);
     }
@@ -1180,6 +1184,7 @@
         DEVICE_MODE_DISABLED, // input is disabled
         DEVICE_MODE_DIRECT, // direct mapping (touchscreen)
         DEVICE_MODE_UNSCALED, // unscaled mapping (touchpad)
+        DEVICE_MODE_NAVIGATION, // unscaled mapping with assist gesture (touch navigation)
         DEVICE_MODE_POINTER, // pointer mapping (pointer)
     };
     DeviceMode mDeviceMode;
@@ -1432,6 +1437,10 @@
     // The maximum swipe width.
     float mPointerGestureMaxSwipeWidth;
 
+    // The start and end Y thresholds for invoking the assist navigation swipe.
+    float mNavigationAssistStartY;
+    float mNavigationAssistEndY;
+
     struct PointerDistanceHeapElement {
         uint32_t currentPointerIndex : 8;
         uint32_t lastPointerIndex : 8;
@@ -1606,6 +1615,15 @@
         }
     } mPointerSimple;
 
+    struct Navigation {
+        // The id of a pointer that is tracking a possible assist swipe.
+        int32_t activeAssistId; // -1 if none
+
+        void reset() {
+            activeAssistId = -1;
+        }
+    } mNavigation;
+
     // The pointer and scroll velocity controls.
     VelocityControl mPointerVelocityControl;
     VelocityControl mWheelXVelocityControl;
@@ -1641,6 +1659,8 @@
             bool down, bool hovering);
     void abortPointerSimple(nsecs_t when, uint32_t policyFlags);
 
+    void dispatchNavigationAssist(nsecs_t when, uint32_t policyFlags);
+
     // Dispatches a motion event.
     // If the changedId is >= 0 and the action is POINTER_DOWN or POINTER_UP, the
     // method will take care of setting the index and transmuting the action to DOWN or UP
diff --git a/services/input/tests/Android.mk b/services/input/tests/Android.mk
index 8f8c34b..211e64b 100644
--- a/services/input/tests/Android.mk
+++ b/services/input/tests/Android.mk
@@ -9,6 +9,7 @@
 
 shared_libraries := \
     libcutils \
+    liblog \
     libandroidfw \
     libutils \
     libhardware \
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 9e06db8..ffc3672 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -321,12 +321,11 @@
 
     // track the current default http proxy - tell the world if we get a new one (real change)
     private ProxyProperties mDefaultProxy = null;
-    private Object mDefaultProxyLock = new Object();
+    private Object mProxyLock = new Object();
     private boolean mDefaultProxyDisabled = false;
 
     // track the global proxy.
     private ProxyProperties mGlobalProxy = null;
-    private final Object mGlobalProxyLock = new Object();
 
     private SettingsObserver mSettingsObserver;
 
@@ -3039,14 +3038,15 @@
         // so this API change wouldn't have a benifit.  It also breaks the passing
         // of proxy info to all the JVMs.
         // enforceAccessPermission();
-        synchronized (mDefaultProxyLock) {
-            return mDefaultProxyDisabled ? null : mDefaultProxy;
+        synchronized (mProxyLock) {
+            if (mGlobalProxy != null) return mGlobalProxy;
+            return (mDefaultProxyDisabled ? null : mDefaultProxy);
         }
     }
 
     public void setGlobalProxy(ProxyProperties proxyProperties) {
         enforceChangePermission();
-        synchronized (mGlobalProxyLock) {
+        synchronized (mProxyLock) {
             if (proxyProperties == mGlobalProxy) return;
             if (proxyProperties != null && proxyProperties.equals(mGlobalProxy)) return;
             if (mGlobalProxy != null && mGlobalProxy.equals(proxyProperties)) return;
@@ -3072,7 +3072,7 @@
         if (mGlobalProxy == null) {
             proxyProperties = mDefaultProxy;
         }
-        //sendProxyBroadcast(proxyProperties);
+        sendProxyBroadcast(proxyProperties);
     }
 
     private void loadGlobalProxy() {
@@ -3083,7 +3083,7 @@
                 Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
         if (!TextUtils.isEmpty(host)) {
             ProxyProperties proxyProperties = new ProxyProperties(host, port, exclList);
-            synchronized (mGlobalProxyLock) {
+            synchronized (mProxyLock) {
                 mGlobalProxy = proxyProperties;
             }
         }
@@ -3094,7 +3094,7 @@
         // so this API change wouldn't have a benifit.  It also breaks the passing
         // of proxy info to all the JVMs.
         // enforceAccessPermission();
-        synchronized (mGlobalProxyLock) {
+        synchronized (mProxyLock) {
             return mGlobalProxy;
         }
     }
@@ -3103,11 +3103,12 @@
         if (proxy != null && TextUtils.isEmpty(proxy.getHost())) {
             proxy = null;
         }
-        synchronized (mDefaultProxyLock) {
+        synchronized (mProxyLock) {
             if (mDefaultProxy != null && mDefaultProxy.equals(proxy)) return;
-            if (mDefaultProxy == proxy) return;
+            if (mDefaultProxy == proxy) return; // catches repeated nulls
             mDefaultProxy = proxy;
 
+            if (mGlobalProxy != null) return;
             if (!mDefaultProxyDisabled) {
                 sendProxyBroadcast(proxy);
             }
@@ -3350,10 +3351,10 @@
                 mDnsOverridden = true;
             }
 
-            // Temporarily disable the default proxy.
-            synchronized (mDefaultProxyLock) {
+            // Temporarily disable the default proxy (not global).
+            synchronized (mProxyLock) {
                 mDefaultProxyDisabled = true;
-                if (mDefaultProxy != null) {
+                if (mGlobalProxy == null && mDefaultProxy != null) {
                     sendProxyBroadcast(null);
                 }
             }
@@ -3368,9 +3369,9 @@
                     mHandler.sendEmptyMessage(EVENT_RESTORE_DNS);
                 }
             }
-            synchronized (mDefaultProxyLock) {
+            synchronized (mProxyLock) {
                 mDefaultProxyDisabled = false;
-                if (mDefaultProxy != null) {
+                if (mGlobalProxy == null && mDefaultProxy != null) {
                     sendProxyBroadcast(mDefaultProxy);
                 }
             }
diff --git a/services/java/com/android/server/wm/AppWindowAnimator.java b/services/java/com/android/server/wm/AppWindowAnimator.java
index 297324b..6293dc6 100644
--- a/services/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/java/com/android/server/wm/AppWindowAnimator.java
@@ -4,6 +4,7 @@
 
 import android.graphics.Matrix;
 import android.util.Slog;
+import android.util.TimeUtils;
 import android.view.Display;
 import android.view.Surface;
 import android.view.SurfaceControl;
@@ -30,6 +31,11 @@
     // Protect with mAnimator.
     boolean freezingScreen;
 
+    /**
+     * How long we last kept the screen frozen.
+     */
+    int lastFreezeDuration;
+
     // Offset to the window of all layers in the token, for use by
     // AppWindowToken animations.
     int animLayerAdjustment;
@@ -287,6 +293,10 @@
         pw.print(prefix); pw.print("freezingScreen="); pw.print(freezingScreen);
                 pw.print(" allDrawn="); pw.print(allDrawn);
                 pw.print(" animLayerAdjustment="); pw.println(animLayerAdjustment);
+        if (lastFreezeDuration != 0) {
+            pw.print(prefix); pw.print("lastFreezeDuration=");
+                    TimeUtils.formatDuration(lastFreezeDuration, pw); pw.println();
+        }
         if (animating || animation != null) {
             pw.print(prefix); pw.print("animating="); pw.println(animating);
             pw.print(prefix); pw.print("animation="); pw.println(animation);
diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java
index 3964782..054a075 100644
--- a/services/java/com/android/server/wm/WindowAnimator.java
+++ b/services/java/com/android/server/wm/WindowAnimator.java
@@ -70,6 +70,7 @@
     int mAboveUniverseLayer = 0;
 
     int mBulkUpdateParams = 0;
+    Object mLastWindowFreezeSource;
 
     SparseArray<DisplayContentsAnimator> mDisplayContentsAnimators =
             new SparseArray<WindowAnimator.DisplayContentsAnimator>();
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index af603fd..1d1fda5 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -43,6 +43,7 @@
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 
 import android.app.AppOpsManager;
+import android.util.TimeUtils;
 import android.view.IWindowId;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.policy.PolicyManager;
@@ -464,6 +465,9 @@
 
     boolean mTraversalScheduled = false;
     boolean mDisplayFrozen = false;
+    long mDisplayFreezeTime = 0;
+    int mLastDisplayFreezeDuration = 0;
+    Object mLastFinishedFreezeSource = null;
     boolean mWaitingForConfig = false;
     boolean mWindowsFreezingScreen = false;
     boolean mClientFreezingScreen = false;
@@ -582,6 +586,7 @@
         boolean mWallpaperForceHidingChanged = false;
         boolean mWallpaperMayChange = false;
         boolean mOrientationChangeComplete = true;
+        Object mLastWindowFreezeSource = null;
         private Session mHoldScreen = null;
         private boolean mObscured = false;
         boolean mDimming = false;
@@ -3590,7 +3595,10 @@
 
         synchronized(mWindowMap) {
             mCurConfiguration = new Configuration(config);
-            mWaitingForConfig = false;
+            if (mWaitingForConfig) {
+                mWaitingForConfig = false;
+                mLastFinishedFreezeSource = "new-config";
+            }
             performLayoutAndPlaceSurfacesLocked();
         }
     }
@@ -4209,6 +4217,7 @@
                         w.mOrientationChanging = true;
                         mInnerFields.mOrientationChangeComplete = false;
                     }
+                    w.mLastFreezeDuration = 0;
                     unfrozeWindows = true;
                     w.mDisplayContent.layoutNeeded = true;
                 }
@@ -4216,7 +4225,10 @@
             if (force || unfrozeWindows) {
                 if (DEBUG_ORIENTATION) Slog.v(TAG, "No longer freezing: " + wtoken);
                 wtoken.mAppAnimator.freezingScreen = false;
+                wtoken.mAppAnimator.lastFreezeDuration = (int)(SystemClock.elapsedRealtime()
+                        - mDisplayFreezeTime);
                 mAppsFreezingScreen--;
+                mLastFinishedFreezeSource = wtoken;
             }
             if (unfreezeSurfaceNow) {
                 if (unfrozeWindows) {
@@ -4242,6 +4254,7 @@
         if (!wtoken.hiddenRequested) {
             if (!wtoken.mAppAnimator.freezingScreen) {
                 wtoken.mAppAnimator.freezingScreen = true;
+                wtoken.mAppAnimator.lastFreezeDuration = 0;
                 mAppsFreezingScreen++;
                 if (mAppsFreezingScreen == 1) {
                     startFreezingDisplayLocked(false, 0, 0);
@@ -4750,6 +4763,7 @@
         synchronized(mWindowMap) {
             if (mClientFreezingScreen) {
                 mClientFreezingScreen = false;
+                mLastFinishedFreezeSource = "client";
                 final long origId = Binder.clearCallingIdentity();
                 try {
                     stopFreezingDisplayLocked();
@@ -5742,6 +5756,7 @@
                 w.mOrientationChanging = true;
                 mInnerFields.mOrientationChangeComplete = false;
             }
+            w.mLastFreezeDuration = 0;
         }
 
         for (int i=mRotationWatchers.size()-1; i>=0; i--) {
@@ -6240,6 +6255,7 @@
             if (config == null && mWaitingForConfig) {
                 // Nothing changed but we are waiting for something... stop that!
                 mWaitingForConfig = false;
+                mLastFinishedFreezeSource = "new-config";
                 performLayoutAndPlaceSurfacesLocked();
             }
             return config;
@@ -7036,6 +7052,8 @@
                             WindowState w = windows.get(i);
                             if (w.mOrientationChanging) {
                                 w.mOrientationChanging = false;
+                                w.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
+                                        - mDisplayFreezeTime);
                                 Slog.w(TAG, "Force clearing orientation change: " + w);
                             }
                         }
@@ -7112,6 +7130,7 @@
                     synchronized (mWindowMap) {
                         if (mClientFreezingScreen) {
                             mClientFreezingScreen = false;
+                            mLastFinishedFreezeSource = "client-timeout";
                             stopFreezingDisplayLocked();
                         }
                     }
@@ -8029,6 +8048,7 @@
             if (DEBUG_ORIENTATION) Slog.v(TAG,
                     "Changing surface while display frozen: " + w);
             w.mOrientationChanging = true;
+            w.mLastFreezeDuration = 0;
             mInnerFields.mOrientationChangeComplete = false;
             if (!mWindowsFreezingScreen) {
                 mWindowsFreezingScreen = true;
@@ -8417,6 +8437,8 @@
                             "Orientation not waiting for draw in "
                             + w + ", surface " + winAnimator.mSurfaceControl);
                     w.mOrientationChanging = false;
+                    w.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
+                            - mDisplayFreezeTime);
                 }
             }
         }
@@ -8930,6 +8952,8 @@
                 winAnimator.mSurfaceResized = false;
             } catch (RemoteException e) {
                 win.mOrientationChanging = false;
+                win.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
+                        - mDisplayFreezeTime);
             }
             mResizingWindows.remove(i);
         }
@@ -8940,6 +8964,7 @@
         if (mInnerFields.mOrientationChangeComplete) {
             if (mWindowsFreezingScreen) {
                 mWindowsFreezingScreen = false;
+                mLastFinishedFreezeSource = mInnerFields.mLastWindowFreezeSource;
                 mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
             }
             stopFreezingDisplayLocked();
@@ -9226,6 +9251,7 @@
             mInnerFields.mOrientationChangeComplete = false;
         } else {
             mInnerFields.mOrientationChangeComplete = true;
+            mInnerFields.mLastWindowFreezeSource = mAnimator.mLastWindowFreezeSource;
             if (mWindowsFreezingScreen) {
                 doRequest = true;
             }
@@ -9498,6 +9524,8 @@
         mScreenFrozenLock.acquire();
 
         mDisplayFrozen = true;
+        mDisplayFreezeTime = SystemClock.elapsedRealtime();
+        mLastFinishedFreezeSource = null;
 
         mInputMonitor.freezeInputDispatchingLw();
 
@@ -9552,6 +9580,15 @@
         }
 
         mDisplayFrozen = false;
+        mLastDisplayFreezeDuration = (int)(SystemClock.elapsedRealtime() - mDisplayFreezeTime);
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("Screen frozen for ");
+        TimeUtils.formatDuration(mLastDisplayFreezeDuration, sb);
+        if (mLastFinishedFreezeSource != null) {
+            sb.append(" due to ");
+            sb.append(mLastFinishedFreezeSource);
+        }
+        Slog.i(TAG, sb.toString());
         mH.removeMessages(H.APP_FREEZE_TIMEOUT);
         mH.removeMessages(H.CLIENT_FREEZE_TIMEOUT);
         if (PROFILE_ORIENTATION) {
@@ -10076,6 +10113,13 @@
         }
         pw.print("  mInTouchMode="); pw.print(mInTouchMode);
                 pw.print(" mLayoutSeq="); pw.println(mLayoutSeq);
+        pw.print("  mLastDisplayFreezeDuration=");
+                TimeUtils.formatDuration(mLastDisplayFreezeDuration, pw);
+                if ( mLastFinishedFreezeSource != null) {
+                    pw.print(" due to ");
+                    pw.print(mLastFinishedFreezeSource);
+                }
+                pw.println();
         if (dumpAll) {
             pw.print("  mSystemDecorRect="); pw.print(mSystemDecorRect.toShortString());
                     pw.print(" mSystemDecorLayer="); pw.print(mSystemDecorLayer);
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index 506fcec..ca060f4 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -26,6 +26,7 @@
 
 import android.app.AppOpsManager;
 import android.os.RemoteCallbackList;
+import android.util.TimeUtils;
 import android.view.IWindowFocusObserver;
 import android.view.IWindowId;
 import com.android.server.input.InputWindowHandle;
@@ -266,6 +267,11 @@
      */
     boolean mOrientationChanging;
 
+    /**
+     * How long we last kept the screen frozen.
+     */
+    int mLastFreezeDuration;
+
     /** Is this window now (or just being) removed? */
     boolean mRemoved;
 
@@ -1387,6 +1393,10 @@
                     pw.print(" mAppFreezing="); pw.print(mAppFreezing);
                     pw.print(" mTurnOnScreen="); pw.println(mTurnOnScreen);
         }
+        if (mLastFreezeDuration != 0) {
+            pw.print(prefix); pw.print("mLastFreezeDuration=");
+                    TimeUtils.formatDuration(mLastFreezeDuration, pw); pw.println();
+        }
         if (mHScale != 1 || mVScale != 1) {
             pw.print(prefix); pw.print("mHScale="); pw.print(mHScale);
                     pw.print(" mVScale="); pw.println(mVScale);
diff --git a/services/java/com/android/server/wm/WindowStateAnimator.java b/services/java/com/android/server/wm/WindowStateAnimator.java
index 3a9f7cb..452f76d 100644
--- a/services/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/java/com/android/server/wm/WindowStateAnimator.java
@@ -1307,6 +1307,7 @@
             if (w.mOrientationChanging) {
                 if (!w.isDrawnLw()) {
                     mAnimator.mBulkUpdateParams &= ~SET_ORIENTATION_CHANGE_COMPLETE;
+                    mAnimator.mLastWindowFreezeSource = w;
                     if (DEBUG_ORIENTATION) Slog.v(TAG,
                             "Orientation continue waiting for draw in " + w);
                 } else {
diff --git a/services/jni/Android.mk b/services/jni/Android.mk
index d097a93..b313d48 100644
--- a/services/jni/Android.mk
+++ b/services/jni/Android.mk
@@ -32,6 +32,7 @@
     libandroid_runtime \
     libandroidfw \
     libcutils \
+    liblog \
     libhardware \
     libhardware_legacy \
     libnativehelper \
diff --git a/telephony/java/android/telephony/CellIdentityCdma.java b/telephony/java/android/telephony/CellIdentityCdma.java
index 6e2a70d..31e01c0 100644
--- a/telephony/java/android/telephony/CellIdentityCdma.java
+++ b/telephony/java/android/telephony/CellIdentityCdma.java
@@ -162,13 +162,13 @@
 
     @Override
     public String toString() {
-        StringBuilder sb = new StringBuilder("CdmaCellIdentitiy:");
-        sb.append(super.toString());
+        StringBuilder sb = new StringBuilder("CellIdentitiyCdma:{");
         sb.append(" mNetworkId="); sb.append(mNetworkId);
         sb.append(" mSystemId="); sb.append(mSystemId);
         sb.append(" mBasestationId="); sb.append(mBasestationId);
         sb.append(" mLongitude="); sb.append(mLongitude);
         sb.append(" mLatitude="); sb.append(mLatitude);
+        sb.append("}");
 
         return sb.toString();
     }
diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java
index bda96be..98113e7 100644
--- a/telephony/java/android/telephony/CellIdentityGsm.java
+++ b/telephony/java/android/telephony/CellIdentityGsm.java
@@ -147,13 +147,13 @@
 
     @Override
     public String toString() {
-        StringBuilder sb = new StringBuilder("GsmCellIdentitiy:");
-        sb.append(super.toString());
+        StringBuilder sb = new StringBuilder("CellIdentitiyGsm:{");
         sb.append(" mMcc=").append(mMcc);
-        sb.append(" mMnc=").append(mMcc);
+        sb.append(" mMnc=").append(mMnc);
         sb.append(" mLac=").append(mLac);
         sb.append(" mCid=").append(mCid);
         sb.append(" mPsc=").append(mPsc);
+        sb.append("}");
 
         return sb.toString();
     }
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index f72d583..86924bd 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -142,13 +142,13 @@
 
     @Override
     public String toString() {
-        StringBuilder sb = new StringBuilder("LteCellIdentitiy:");
-        sb.append(super.toString());
+        StringBuilder sb = new StringBuilder("CellIdentitiyLte:{");
         sb.append(" mMcc="); sb.append(mMcc);
         sb.append(" mMnc="); sb.append(mMnc);
         sb.append(" mCi="); sb.append(mCi);
         sb.append(" mPci="); sb.append(mPci);
         sb.append(" mTac="); sb.append(mTac);
+        sb.append("}");
 
         return sb.toString();
     }
diff --git a/telephony/java/android/telephony/CellInfo.java b/telephony/java/android/telephony/CellInfo.java
index f367f99..fe3c68b 100644
--- a/telephony/java/android/telephony/CellInfo.java
+++ b/telephony/java/android/telephony/CellInfo.java
@@ -149,7 +149,7 @@
         StringBuffer sb = new StringBuffer();
         String timeStampType;
 
-        sb.append(" mRegistered=").append(mRegistered ? "YES" : "NO");
+        sb.append("mRegistered=").append(mRegistered ? "YES" : "NO");
         timeStampType = timeStampTypeToString(mTimeStampType);
         sb.append(" mTimeStampType=").append(timeStampType);
         sb.append(" mTimeStamp=").append(mTimeStamp).append("ns");
diff --git a/telephony/java/android/telephony/CellInfoCdma.java b/telephony/java/android/telephony/CellInfoCdma.java
index a5d6e9c..6f2f1f6 100644
--- a/telephony/java/android/telephony/CellInfoCdma.java
+++ b/telephony/java/android/telephony/CellInfoCdma.java
@@ -87,10 +87,11 @@
     public String toString() {
         StringBuffer sb = new StringBuffer();
 
-        sb.append("CellInfoCdma:");
+        sb.append("CellInfoCdma:{");
         sb.append(super.toString());
-        sb.append(", ").append(mCellIdentityCdma);
-        sb.append(", ").append(mCellSignalStrengthCdma);
+        sb.append(" ").append(mCellIdentityCdma);
+        sb.append(" ").append(mCellSignalStrengthCdma);
+        sb.append("}");
 
         return sb.toString();
     }
diff --git a/telephony/java/android/telephony/CellInfoGsm.java b/telephony/java/android/telephony/CellInfoGsm.java
index bf0eca8..1bedddb 100644
--- a/telephony/java/android/telephony/CellInfoGsm.java
+++ b/telephony/java/android/telephony/CellInfoGsm.java
@@ -87,10 +87,11 @@
     public String toString() {
         StringBuffer sb = new StringBuffer();
 
-        sb.append("CellInfoGsm:");
+        sb.append("CellInfoGsm:{");
         sb.append(super.toString());
-        sb.append(", ").append(mCellIdentityGsm);
-        sb.append(", ").append(mCellSignalStrengthGsm);
+        sb.append(" ").append(mCellIdentityGsm);
+        sb.append(" ").append(mCellSignalStrengthGsm);
+        sb.append("}");
 
         return sb.toString();
     }
diff --git a/telephony/java/android/telephony/CellInfoLte.java b/telephony/java/android/telephony/CellInfoLte.java
index d7a58b6..287c9f0 100644
--- a/telephony/java/android/telephony/CellInfoLte.java
+++ b/telephony/java/android/telephony/CellInfoLte.java
@@ -91,10 +91,11 @@
     public String toString() {
         StringBuffer sb = new StringBuffer();
 
-        sb.append("CellInfoLte:");
+        sb.append("CellInfoLte:{");
         sb.append(super.toString());
-        sb.append(", ").append(mCellIdentityLte);
-        sb.append(", ").append(mCellSignalStrengthLte);
+        sb.append(" ").append(mCellIdentityLte);
+        sb.append(" ").append(mCellSignalStrengthLte);
+        sb.append("}");
 
         return sb.toString();
     }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 4aee902..6400e68 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1341,7 +1341,8 @@
     }
 
     /**
-     * Returns all observed cell information of the device.
+     * Returns all observed cell information of the device. This does
+     * not cause or change the rate of PhoneStateListner#onCellInfoChanged.
      *
      * @return List of CellInfo or null if info unavailable.
      *
@@ -1357,4 +1358,24 @@
             return null;
         }
     }
+
+    /**
+     * Sets the minimum time in milli-seconds between {@link PhoneStateListener#onCellInfoChanged
+     * PhoneStateListener.onCellInfoChanged} will be invoked.
+     *
+     * The default, 0, means invoke onCellInfoChanged when any of the reported
+     * information changes. Setting the value to INT_MAX(0x7fffffff) means never issue
+     * A onCellInfoChanged.
+     *
+     * @param rateInMillis the rate
+     *
+     * @hide
+     */
+    public void setCellInfoListRate(int rateInMillis) {
+        try {
+            getITelephony().setCellInfoListRate(rateInMillis);
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 1449ab1..b78f589 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -294,5 +294,10 @@
      * Returns the all observed cell information of the device.
      */
     List<CellInfo> getAllCellInfo();
+
+    /**
+     * Sets minimum time in milli-seconds between onCellInfoChanged
+     */
+    void setCellInfoListRate(int rateInMillis);
 }
 
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 077ad68..9650b99 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -260,6 +260,8 @@
     int RIL_REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU = 106;
     int RIL_REQUEST_STK_SEND_ENVELOPE_WITH_STATUS = 107;
     int RIL_REQUEST_VOICE_RADIO_TECH = 108;
+    int RIL_REQUEST_GET_CELL_INFO_LIST = 109;
+    int RIL_REQUEST_SET_UNSOL_CELL_INFO_LIST_RATE = 110;
     int RIL_UNSOL_RESPONSE_BASE = 1000;
     int RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED = 1000;
     int RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED = 1001;
@@ -297,4 +299,5 @@
     int RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE = 1033;
     int RIL_UNSOL_RIL_CONNECTED = 1034;
     int RIL_UNSOL_VOICE_RADIO_TECH_CHANGED = 1035;
+    int RIL_UNSOL_CELL_INFO_LIST = 1036;
 }
diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk
index 5b88669..9b1658a 100644
--- a/tools/aapt/Android.mk
+++ b/tools/aapt/Android.mk
@@ -39,14 +39,14 @@
 LOCAL_C_INCLUDES += external/zlib
 LOCAL_C_INCLUDES += build/libs/host/include
 
-#LOCAL_WHOLE_STATIC_LIBRARIES := 
 LOCAL_STATIC_LIBRARIES := \
 	libhost \
 	libandroidfw \
 	libutils \
 	libcutils \
 	libexpat \
-	libpng
+	libpng \
+	liblog
 
 ifeq ($(HOST_OS),linux)
 LOCAL_LDLIBS += -lrt -ldl -lpthread
diff --git a/tools/obbtool/Android.mk b/tools/obbtool/Android.mk
index dd57ae6..ad8de69 100644
--- a/tools/obbtool/Android.mk
+++ b/tools/obbtool/Android.mk
@@ -20,7 +20,8 @@
 LOCAL_STATIC_LIBRARIES := \
 	libutils \
 	libandroidfw \
-	libcutils
+	libcutils \
+	liblog
 
 ifeq ($(HOST_OS),linux)
 LOCAL_LDLIBS += -ldl -lpthread
diff --git a/tools/validatekeymaps/Android.mk b/tools/validatekeymaps/Android.mk
index fce2e93..90fbc08 100644
--- a/tools/validatekeymaps/Android.mk
+++ b/tools/validatekeymaps/Android.mk
@@ -20,7 +20,8 @@
 LOCAL_STATIC_LIBRARIES := \
 	libandroidfw \
 	libutils \
-	libcutils
+	libcutils \
+	liblog
 
 ifeq ($(HOST_OS),linux)
 LOCAL_LDLIBS += -ldl -lpthread
diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java
index 47f1fbf..7b1a71f 100644
--- a/wifi/java/android/net/wifi/WifiConfigStore.java
+++ b/wifi/java/android/net/wifi/WifiConfigStore.java
@@ -1196,7 +1196,7 @@
             WifiConfiguration newConfig) {
         boolean ipChanged = false;
         boolean proxyChanged = false;
-        LinkProperties linkProperties = new LinkProperties();
+        LinkProperties linkProperties = null;
 
         switch (newConfig.ipAssignment) {
             case STATIC:
@@ -1262,10 +1262,10 @@
         }
 
         if (!ipChanged) {
-            addIpSettingsFromConfig(linkProperties, currentConfig);
+            linkProperties = copyIpSettingsFromConfig(currentConfig);
         } else {
             currentConfig.ipAssignment = newConfig.ipAssignment;
-            addIpSettingsFromConfig(linkProperties, newConfig);
+            linkProperties = copyIpSettingsFromConfig(newConfig);
             log("IP config changed SSID = " + currentConfig.SSID + " linkProperties: " +
                     linkProperties.toString());
         }
@@ -1291,8 +1291,9 @@
         return new NetworkUpdateResult(ipChanged, proxyChanged);
     }
 
-    private void addIpSettingsFromConfig(LinkProperties linkProperties,
-            WifiConfiguration config) {
+    private LinkProperties copyIpSettingsFromConfig(WifiConfiguration config) {
+        LinkProperties linkProperties = new LinkProperties();
+        linkProperties.setInterfaceName(config.linkProperties.getInterfaceName());
         for (LinkAddress linkAddr : config.linkProperties.getLinkAddresses()) {
             linkProperties.addLinkAddress(linkAddr);
         }
@@ -1302,6 +1303,7 @@
         for (InetAddress dns : config.linkProperties.getDnses()) {
             linkProperties.addDns(dns);
         }
+        return linkProperties;
     }
 
     /**