Merge "3012761 Please fix problems with your strings"
diff --git a/api/current.xml b/api/current.xml
index e3b6a01..aadffa3 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -28771,17 +28771,6 @@
  visibility="public"
 >
 </field>
-<field name="COLUMN_ERROR_CODE"
- type="java.lang.String"
- transient="false"
- volatile="false"
- value="&quot;error_code&quot;"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
 <field name="COLUMN_ID"
  type="java.lang.String"
  transient="false"
@@ -28826,6 +28815,17 @@
  visibility="public"
 >
 </field>
+<field name="COLUMN_REASON"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;reason&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="COLUMN_STATUS"
  type="java.lang.String"
  transient="false"
@@ -28980,6 +28980,50 @@
  visibility="public"
 >
 </field>
+<field name="PAUSED_QUEUED_FOR_WIFI"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PAUSED_UNKNOWN"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PAUSED_WAITING_FOR_NETWORK"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PAUSED_WAITING_TO_RETRY"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="STATUS_FAILED"
  type="int"
  transient="false"
@@ -61065,6 +61109,16 @@
  visibility="public"
 >
 </field>
+<field name="filename"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="flags"
  type="int"
  transient="false"
@@ -139818,7 +139872,7 @@
  visibility="public"
 >
 <method name="allowThreadDiskReads"
- return="int"
+ return="android.os.StrictMode.ThreadPolicy"
  abstract="false"
  native="false"
  synchronized="false"
@@ -139829,7 +139883,7 @@
 >
 </method>
 <method name="allowThreadDiskWrites"
- return="int"
+ return="android.os.StrictMode.ThreadPolicy"
  abstract="false"
  native="false"
  synchronized="false"
@@ -139840,7 +139894,18 @@
 >
 </method>
 <method name="getThreadPolicy"
- return="int"
+ return="android.os.StrictMode.ThreadPolicy"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVmPolicy"
+ return="android.os.StrictMode.VmPolicy"
  abstract="false"
  native="false"
  synchronized="false"
@@ -139860,86 +139925,313 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="policyMask" type="int">
+<parameter name="policy" type="android.os.StrictMode.ThreadPolicy">
 </parameter>
 </method>
-<field name="DISALLOW_DISK_READ"
- type="int"
+<method name="setVmPolicy"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="policy" type="android.os.StrictMode.VmPolicy">
+</parameter>
+</method>
+</class>
+<class name="StrictMode.ThreadPolicy"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<field name="LAX"
+ type="android.os.StrictMode.ThreadPolicy"
  transient="false"
  volatile="false"
- value="2"
  static="true"
  final="true"
  deprecated="not deprecated"
  visibility="public"
 >
 </field>
-<field name="DISALLOW_DISK_WRITE"
- type="int"
+</class>
+<class name="StrictMode.ThreadPolicy.Builder"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="StrictMode.ThreadPolicy.Builder"
+ type="android.os.StrictMode.ThreadPolicy.Builder"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<constructor name="StrictMode.ThreadPolicy.Builder"
+ type="android.os.StrictMode.ThreadPolicy.Builder"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="policy" type="android.os.StrictMode.ThreadPolicy">
+</parameter>
+</constructor>
+<method name="build"
+ return="android.os.StrictMode.ThreadPolicy"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="detectAll"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="detectDiskReads"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="detectDiskWrites"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="detectNetwork"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="penaltyDeath"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="penaltyDialog"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="penaltyDropBox"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="penaltyLog"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="permitAll"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="permitDiskReads"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="permitDiskWrites"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="permitNetwork"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
+<class name="StrictMode.VmPolicy"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<field name="LAX"
+ type="android.os.StrictMode.VmPolicy"
  transient="false"
  volatile="false"
- value="1"
  static="true"
  final="true"
  deprecated="not deprecated"
  visibility="public"
 >
 </field>
-<field name="DISALLOW_NETWORK"
- type="int"
- transient="false"
- volatile="false"
- value="4"
+</class>
+<class name="StrictMode.VmPolicy.Builder"
+ extends="java.lang.Object"
+ abstract="false"
  static="true"
  final="true"
  deprecated="not deprecated"
  visibility="public"
 >
-</field>
-<field name="PENALTY_DEATH"
- type="int"
- transient="false"
- volatile="false"
- value="64"
- static="true"
- final="true"
+<constructor name="StrictMode.VmPolicy.Builder"
+ type="android.os.StrictMode.VmPolicy.Builder"
+ static="false"
+ final="false"
  deprecated="not deprecated"
  visibility="public"
 >
-</field>
-<field name="PENALTY_DIALOG"
- type="int"
- transient="false"
- volatile="false"
- value="32"
- static="true"
- final="true"
+</constructor>
+<method name="build"
+ return="android.os.StrictMode.VmPolicy"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
  deprecated="not deprecated"
  visibility="public"
 >
-</field>
-<field name="PENALTY_DROPBOX"
- type="int"
- transient="false"
- volatile="false"
- value="128"
- static="true"
- final="true"
+</method>
+<method name="detectAll"
+ return="android.os.StrictMode.VmPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
  deprecated="not deprecated"
  visibility="public"
 >
-</field>
-<field name="PENALTY_LOG"
- type="int"
- transient="false"
- volatile="false"
- value="16"
- static="true"
- final="true"
+</method>
+<method name="detectLeakedSqlLiteObjects"
+ return="android.os.StrictMode.VmPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
  deprecated="not deprecated"
  visibility="public"
 >
-</field>
+</method>
+<method name="penaltyDeath"
+ return="android.os.StrictMode.VmPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="penaltyDropBox"
+ return="android.os.StrictMode.VmPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="penaltyLog"
+ return="android.os.StrictMode.VmPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 </class>
 <class name="SystemClock"
  extends="java.lang.Object"
@@ -140501,8 +140793,6 @@
 </parameter>
 <parameter name="listener" type="android.os.storage.OnObbStateChangeListener">
 </parameter>
-<exception name="IllegalArgumentException" type="java.lang.IllegalArgumentException">
-</exception>
 </method>
 <method name="unregisterListener"
  return="void"
@@ -189568,6 +189858,217 @@
 >
 </field>
 </class>
+<class name="DragEvent"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getAction"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getClipData"
+ return="android.content.ClipData"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getClipDescription"
+ return="android.content.ClipDescription"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getX"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getY"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="obtain"
+ return="android.view.DragEvent"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="obtain"
+ return="android.view.DragEvent"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="action" type="int">
+</parameter>
+<parameter name="x" type="float">
+</parameter>
+<parameter name="y" type="float">
+</parameter>
+<parameter name="description" type="android.content.ClipDescription">
+</parameter>
+<parameter name="data" type="android.content.ClipData">
+</parameter>
+</method>
+<method name="recycle"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="writeToParcel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="dest" type="android.os.Parcel">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<field name="ACTION_DRAG_ENDED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_DRAG_ENTERED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_DRAG_EXITED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="6"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_DRAG_LOCATION"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_DRAG_STARTED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_DROP"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CREATOR"
+ type="android.os.Parcelable.Creator"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
 <class name="FocusFinder"
  extends="java.lang.Object"
  abstract="false"
@@ -195957,6 +196458,19 @@
 <parameter name="y" type="float">
 </parameter>
 </method>
+<method name="transform"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="matrix" type="android.graphics.Matrix">
+</parameter>
+</method>
 <method name="writeToParcel"
  return="void"
  abstract="false"
@@ -198511,6 +199025,19 @@
 <parameter name="hint" type="int">
 </parameter>
 </method>
+<method name="dispatchDragEvent"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="event" type="android.view.DragEvent">
+</parameter>
+</method>
 <method name="dispatchDraw"
  return="void"
  abstract="false"
@@ -200476,6 +201003,19 @@
 <parameter name="hint" type="int">
 </parameter>
 </method>
+<method name="onDragEvent"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="event" type="android.view.DragEvent">
+</parameter>
+</method>
 <method name="onDraw"
  return="void"
  abstract="false"
@@ -200489,6 +201029,19 @@
 <parameter name="canvas" type="android.graphics.Canvas">
 </parameter>
 </method>
+<method name="onDrawDragThumbnail"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="canvas" type="android.graphics.Canvas">
+</parameter>
+</method>
 <method name="onDrawScrollBars"
  return="void"
  abstract="false"
@@ -200682,6 +201235,17 @@
 <parameter name="heightMeasureSpec" type="int">
 </parameter>
 </method>
+<method name="onMeasureDragThumbnail"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</method>
 <method name="onRestoreInstanceState"
  return="void"
  abstract="false"
@@ -201347,6 +201911,21 @@
 <parameter name="contentDescription" type="java.lang.CharSequence">
 </parameter>
 </method>
+<method name="setDragThumbnailDimension"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="width" type="int">
+</parameter>
+<parameter name="height" type="int">
+</parameter>
+</method>
 <method name="setDrawingCacheBackgroundColor"
  return="void"
  abstract="false"
@@ -241659,7 +242238,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="arg0" type="T">
+<parameter name="t" type="T">
 </parameter>
 </method>
 </interface>
@@ -249418,7 +249997,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="n" type="long">
+<parameter name="byteCount" type="long">
 </parameter>
 <exception name="IOException" type="java.io.IOException">
 </exception>
@@ -283808,9 +284387,9 @@
  deprecated="not deprecated"
  visibility="protected"
 >
-<parameter name="url1" type="java.net.URL">
+<parameter name="a" type="java.net.URL">
 </parameter>
-<parameter name="url2" type="java.net.URL">
+<parameter name="b" type="java.net.URL">
 </parameter>
 </method>
 <method name="openConnection"
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 39b3a20..37c8ad0 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -34,7 +34,6 @@
 
     private String[] mArgs;
     private int mNextArg;
-    private String mCurArgData;
 
     public static void main(String[] args) {
         try {
@@ -274,6 +273,10 @@
     }
 
     private void printRestoreSets(RestoreSet[] sets) {
+        if (sets == null || sets.length == 0) {
+            System.out.println("No restore sets");
+            return;
+        }
         for (RestoreSet s : sets) {
             System.out.println("  " + Long.toHexString(s.token) + " : " + s.name);
         }
diff --git a/cmds/keystore/keystore.c b/cmds/keystore/keystore.c
index 60cc521..971a177 100644
--- a/cmds/keystore/keystore.c
+++ b/cmds/keystore/keystore.c
@@ -166,7 +166,7 @@
     int length;
     int fd;
 
-    if (read(the_entropy, vector, AES_BLOCK_SIZE) != AES_BLOCK_SIZE) {
+    if (read(the_entropy, blob.vector, AES_BLOCK_SIZE) != AES_BLOCK_SIZE) {
         return SYSTEM_ERROR;
     }
 
diff --git a/cmds/keystore/keystore_get.h b/cmds/keystore/keystore_get.h
index 141f69b..4b4923e 100644
--- a/cmds/keystore/keystore_get.h
+++ b/cmds/keystore/keystore_get.h
@@ -32,7 +32,7 @@
 #endif
 
 /* This function is provided for native components to get values from keystore.
- * Users are required to link against libcutils. Keys are values are 8-bit safe.
+ * Users are required to link against libcutils. Keys and values are 8-bit safe.
  * The first two arguments are the key and its length. The third argument
  * specifies the buffer to store the retrieved value, which must be an array of
  * KEYSTORE_MESSAGE_SIZE bytes. This function returns the length of the value or
@@ -65,7 +65,10 @@
             }
             offset += n;
         }
+    } else {
+        length = -1;
     }
+
     close(sock);
     return length;
 }
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 6ce5b86..bc5e10d 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -17,32 +17,21 @@
 #include <unistd.h>
 #include <fcntl.h>
 
-#include <utils/Log.h>
-
-#include <binder/IPCThreadState.h>
-#include <binder/ProcessState.h>
-#include <binder/IServiceManager.h>
-
 #include <binder/IMemory.h>
-#include <surfaceflinger/ISurfaceComposer.h>
+#include <surfaceflinger/SurfaceComposerClient.h>
 
 using namespace android;
 
 int main(int argc, char** argv)
 {
-    const String16 name("SurfaceFlinger");
-    sp<ISurfaceComposer> composer;
-    if (getService(name, &composer) != NO_ERROR)
+    ScreenshotClient screenshot;
+    if (screenshot.update() != NO_ERROR)
         return 0;
 
-    sp<IMemoryHeap> heap;
-    uint32_t w, h;
-    PixelFormat f;
-    status_t err = composer->captureScreen(0, &heap, &w, &h, &f);
-    if (err != NO_ERROR)
-        return 0;
-
-    uint8_t* base = (uint8_t*)heap->getBase();
+    void const* base = screenshot.getPixels();
+    uint32_t w = screenshot.getWidth();
+    uint32_t h = screenshot.getHeight();
+    uint32_t f = screenshot.getFormat();
     int fd = dup(STDOUT_FILENO);
     write(fd, &w, 4);
     write(fd, &h, 4);
diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk
index 9a97284..cbdf119 100644
--- a/cmds/stagefright/Android.mk
+++ b/cmds/stagefright/Android.mk
@@ -53,6 +53,31 @@
 
 LOCAL_SRC_FILES:=         \
         SineSource.cpp    \
+        recordvideo.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+	libstagefright liblog libutils libbinder
+
+LOCAL_C_INCLUDES:= \
+	$(JNI_H_INCLUDE) \
+	frameworks/base/media/libstagefright \
+	$(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include
+
+LOCAL_CFLAGS += -Wno-multichar
+
+LOCAL_MODULE_TAGS := debug
+
+LOCAL_MODULE:= recordvideo
+
+include $(BUILD_EXECUTABLE)
+
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:=         \
+        SineSource.cpp    \
         audioloop.cpp
 
 LOCAL_SHARED_LIBRARIES := \
diff --git a/cmds/stagefright/recordvideo.cpp b/cmds/stagefright/recordvideo.cpp
new file mode 100644
index 0000000..330fbc2
--- /dev/null
+++ b/cmds/stagefright/recordvideo.cpp
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SineSource.h"
+
+#include <binder/ProcessState.h>
+#include <media/stagefright/AudioPlayer.h>
+#include <media/stagefright/FileSource.h>
+#include <media/stagefright/MediaBufferGroup.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MPEG4Writer.h>
+#include <media/stagefright/OMXClient.h>
+#include <media/stagefright/OMXCodec.h>
+#include <media/MediaPlayerInterface.h>
+
+using namespace android;
+
+// print usage showing how to use this utility to record videos
+static void usage(const char *me) {
+    fprintf(stderr, "usage: %s\n", me);
+    fprintf(stderr, "       -h(elp)\n");
+    fprintf(stderr, "       -b bit rate in bits per second (default 300000)\n");
+    fprintf(stderr, "       -c YUV420 color format: [0] semi planar or [1] planar (default 1)\n");
+    fprintf(stderr, "       -f frame rate in frames per second (default 30)\n");
+    fprintf(stderr, "       -i I frame interval in seconds (default 1)\n");
+    fprintf(stderr, "       -n number of frames to be recorded (default 300)\n");
+    fprintf(stderr, "       -w width in pixels (default 176)\n");
+    fprintf(stderr, "       -t height in pixels (default 144)\n");
+    fprintf(stderr, "       -v video codec: [0] AVC [1] M4V [2] H263 (default 0)\n");
+    exit(1);
+}
+
+class DummySource : public MediaSource {
+
+public:
+    DummySource(int width, int height, int nFrames, int fps, int colorFormat)
+        : mWidth(width),
+          mHeight(height),
+          mMaxNumFrames(nFrames),
+          mFrameRate(fps),
+          mColorFormat(colorFormat),
+          mSize((width * height * 3) / 2) {
+        mGroup.add_buffer(new MediaBuffer(mSize));
+
+        // Check the color format to make sure
+        // that the buffer size mSize it set correctly above.
+        CHECK(colorFormat == OMX_COLOR_FormatYUV420SemiPlanar ||
+              colorFormat == OMX_COLOR_FormatYUV420Planar);
+    }
+
+    virtual sp<MetaData> getFormat() {
+        sp<MetaData> meta = new MetaData;
+        meta->setInt32(kKeyWidth, mWidth);
+        meta->setInt32(kKeyHeight, mHeight);
+        meta->setInt32(kKeyColorFormat, mColorFormat);
+        meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RAW);
+
+        return meta;
+    }
+
+    virtual status_t start(MetaData *params) {
+        mNumFramesOutput = 0;
+        return OK;
+    }
+
+    virtual status_t stop() {
+        return OK;
+    }
+
+    virtual status_t read(
+            MediaBuffer **buffer, const MediaSource::ReadOptions *options) {
+
+        if (mNumFramesOutput % 10 == 0) {
+            fprintf(stderr, ".");
+        }
+        if (mNumFramesOutput == mMaxNumFrames) {
+            return ERROR_END_OF_STREAM;
+        }
+
+        status_t err = mGroup.acquire_buffer(buffer);
+        if (err != OK) {
+            return err;
+        }
+
+        char x = (char)((double)rand() / RAND_MAX * 255);
+        memset((*buffer)->data(), x, mSize);
+        (*buffer)->set_range(0, mSize);
+        (*buffer)->meta_data()->clear();
+        (*buffer)->meta_data()->setInt64(
+                kKeyTime, (mNumFramesOutput * 1000000) / mFrameRate);
+        ++mNumFramesOutput;
+
+        return OK;
+    }
+
+protected:
+    virtual ~DummySource() {}
+
+private:
+    MediaBufferGroup mGroup;
+    int mWidth, mHeight;
+    int mMaxNumFrames;
+    int mFrameRate;
+    int mColorFormat;
+    size_t mSize;
+    int64_t mNumFramesOutput;;
+
+    DummySource(const DummySource &);
+    DummySource &operator=(const DummySource &);
+};
+
+sp<MediaSource> createSource(const char *filename) {
+    sp<MediaSource> source;
+
+    sp<MediaExtractor> extractor =
+        MediaExtractor::Create(new FileSource(filename));
+    if (extractor == NULL) {
+        return NULL;
+    }
+
+    size_t num_tracks = extractor->countTracks();
+
+    sp<MetaData> meta;
+    for (size_t i = 0; i < num_tracks; ++i) {
+        meta = extractor->getTrackMetaData(i);
+        CHECK(meta.get() != NULL);
+
+        const char *mime;
+        if (!meta->findCString(kKeyMIMEType, &mime)) {
+            continue;
+        }
+
+        if (strncasecmp(mime, "video/", 6)) {
+            continue;
+        }
+
+        source = extractor->getTrack(i);
+        break;
+    }
+
+    return source;
+}
+
+enum {
+    kYUV420SP = 0,
+    kYUV420P  = 1,
+};
+
+// returns -1 if mapping of the given color is unsuccessful
+// returns an omx color enum value otherwise
+static int translateColorToOmxEnumValue(int color) {
+    switch (color) {
+        case kYUV420SP:
+            return OMX_COLOR_FormatYUV420SemiPlanar;
+        case kYUV420P:
+            return OMX_COLOR_FormatYUV420Planar;
+        default:
+            fprintf(stderr, "Unsupported color: %d\n", color);
+            return -1;
+    }
+}
+
+int main(int argc, char **argv) {
+
+    // Default values for the program if not overwritten
+    int frameRateFps = 30;
+    int width = 176;
+    int height = 144;
+    int bitRateBps = 300000;
+    int iFramesIntervalSeconds = 1;
+    int colorFormat = OMX_COLOR_FormatYUV420Planar;
+    int nFrames = 300;
+    int codec = 0;
+    const char *fileName = "/sdcard/output.mp4";
+
+    android::ProcessState::self()->startThreadPool();
+    int res;
+    while ((res = getopt(argc, argv, "b:c:f:i:n:w:t:v:o:h")) >= 0) {
+        switch (res) {
+            case 'b':
+            {
+                bitRateBps = atoi(optarg);
+                break;
+            }
+
+            case 'c':
+            {
+                colorFormat = translateColorToOmxEnumValue(atoi(optarg));
+                if (colorFormat == -1) {
+                    usage(argv[0]);
+                }
+                break;
+            }
+
+            case 'f':
+            {
+                frameRateFps = atoi(optarg);
+                break;
+            }
+
+            case 'i':
+            {
+                iFramesIntervalSeconds = atoi(optarg);
+                break;
+            }
+
+            case 'n':
+            {
+                nFrames = atoi(optarg);
+                break;
+            }
+
+            case 'w':
+            {
+                width = atoi(optarg);
+                break;
+            }
+
+            case 't':
+            {
+                height = atoi(optarg);
+                break;
+            }
+
+            case 'v':
+            {
+                codec = atoi(optarg);
+                if (codec < 0 || codec > 2) {
+                    usage(argv[0]);
+                }
+                break;
+            }
+
+            case 'h':
+            default:
+            {
+                usage(argv[0]);
+                break;
+            }
+        }
+    }
+
+    OMXClient client;
+    CHECK_EQ(client.connect(), OK);
+
+    status_t err = OK;
+    sp<MediaSource> decoder = new DummySource(width, height, nFrames, frameRateFps, colorFormat);
+
+    sp<MetaData> enc_meta = new MetaData;
+    switch (codec) {
+        case 1:
+            enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4);
+            break;
+        case 2:
+            enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263);
+            break;
+        default:
+            enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
+            break;
+    }
+    enc_meta->setInt32(kKeyWidth, width);
+    enc_meta->setInt32(kKeyHeight, height);
+    enc_meta->setInt32(kKeySampleRate, frameRateFps);
+    enc_meta->setInt32(kKeyBitRate, bitRateBps);
+    enc_meta->setInt32(kKeyStride, width);
+    enc_meta->setInt32(kKeySliceHeight, height);
+    enc_meta->setInt32(kKeyIFramesInterval, iFramesIntervalSeconds);
+    enc_meta->setInt32(kKeyColorFormat, colorFormat);
+
+    sp<MediaSource> encoder =
+        OMXCodec::Create(
+                client.interface(), enc_meta, true /* createEncoder */, decoder);
+
+    sp<MPEG4Writer> writer = new MPEG4Writer(fileName);
+    writer->addSource(encoder);
+    int64_t start = systemTime();
+    CHECK_EQ(OK, writer->start());
+    while (!writer->reachedEOS()) {
+    }
+    err = writer->stop();
+    int64_t end = systemTime();
+
+    printf("$\n");
+    client.disconnect();
+
+    if (err != OK && err != ERROR_END_OF_STREAM) {
+        fprintf(stderr, "record failed: %d\n", err);
+        return 1;
+    }
+    fprintf(stderr, "encoding %d frames in %lld us\n", nFrames, (end-start)/1000);
+    fprintf(stderr, "encoding speed is: %.2f fps\n", (nFrames * 1E9) / (end-start));
+    return 0;
+}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b8bbc88..df18ce7 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3352,7 +3352,7 @@
         while (i.hasNext()) {
             ProviderInfo cpi = i.next();
             StringBuilder buf = new StringBuilder(128);
-            buf.append("Publishing provider ");
+            buf.append("Pub ");
             buf.append(cpi.authority);
             buf.append(": ");
             buf.append(cpi.name);
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 69c99cc..013032c 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -47,6 +47,10 @@
  * Instances of this class should be obtained through
  * {@link android.content.Context#getSystemService(String)} by passing
  * {@link android.content.Context#DOWNLOAD_SERVICE}.
+ *
+ * Apps that request downloads through this API should register a broadcast receiver for
+ * {@link #ACTION_NOTIFICATION_CLICKED} to appropriately handle when the user clicks on a running
+ * download in a notification or from the downloads UI.
  */
 public class DownloadManager {
     /**
@@ -100,16 +104,23 @@
     public final static String COLUMN_STATUS = "status";
 
     /**
-     * Indicates the type of error that occurred, when {@link #COLUMN_STATUS} is
-     * {@link #STATUS_FAILED}.  If an HTTP error occurred, this will hold the HTTP status code as
-     * defined in RFC 2616.  Otherwise, it will hold one of the ERROR_* constants.
+     * Provides more detail on the status of the download.  Its meaning depends on the value of
+     * {@link #COLUMN_STATUS}.
      *
-     * If {@link #COLUMN_STATUS} is not {@link #STATUS_FAILED}, this column's value is undefined.
+     * When {@link #COLUMN_STATUS} is {@link #STATUS_FAILED}, this indicates the type of error that
+     * occurred.  If an HTTP error occurred, this will hold the HTTP status code as defined in RFC
+     * 2616.  Otherwise, it will hold one of the ERROR_* constants.
+     *
+     * When {@link #COLUMN_STATUS} is {@link #STATUS_PAUSED}, this indicates why the download is
+     * paused.  It will hold one of the PAUSED_* constants.
+     *
+     * If {@link #COLUMN_STATUS} is neither {@link #STATUS_FAILED} nor {@link #STATUS_PAUSED}, this
+     * column's value is undefined.
      *
      * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1">RFC 2616
      * status codes</a>
      */
-    public final static String COLUMN_ERROR_CODE = "error_code";
+    public final static String COLUMN_REASON = "reason";
 
     /**
      * Number of bytes download so far.
@@ -156,61 +167,84 @@
     public final static int ERROR_UNKNOWN = 1000;
 
     /**
-     * Value of {@link #COLUMN_ERROR_CODE} when a storage issue arises which doesn't fit under any
+     * Value of {@link #COLUMN_REASON} when a storage issue arises which doesn't fit under any
      * other error code. Use the more specific {@link #ERROR_INSUFFICIENT_SPACE} and
      * {@link #ERROR_DEVICE_NOT_FOUND} when appropriate.
      */
     public final static int ERROR_FILE_ERROR = 1001;
 
     /**
-     * Value of {@link #COLUMN_ERROR_CODE} when an HTTP code was received that download manager
+     * Value of {@link #COLUMN_REASON} when an HTTP code was received that download manager
      * can't handle.
      */
     public final static int ERROR_UNHANDLED_HTTP_CODE = 1002;
 
     /**
-     * Value of {@link #COLUMN_ERROR_CODE} when an error receiving or processing data occurred at
+     * Value of {@link #COLUMN_REASON} when an error receiving or processing data occurred at
      * the HTTP level.
      */
     public final static int ERROR_HTTP_DATA_ERROR = 1004;
 
     /**
-     * Value of {@link #COLUMN_ERROR_CODE} when there were too many redirects.
+     * Value of {@link #COLUMN_REASON} when there were too many redirects.
      */
     public final static int ERROR_TOO_MANY_REDIRECTS = 1005;
 
     /**
-     * Value of {@link #COLUMN_ERROR_CODE} when there was insufficient storage space. Typically,
+     * Value of {@link #COLUMN_REASON} when there was insufficient storage space. Typically,
      * this is because the SD card is full.
      */
     public final static int ERROR_INSUFFICIENT_SPACE = 1006;
 
     /**
-     * Value of {@link #COLUMN_ERROR_CODE} when no external storage device was found. Typically,
+     * Value of {@link #COLUMN_REASON} when no external storage device was found. Typically,
      * this is because the SD card is not mounted.
      */
     public final static int ERROR_DEVICE_NOT_FOUND = 1007;
 
     /**
-     * Value of {@link #COLUMN_ERROR_CODE} when some possibly transient error occurred but we can't
+     * Value of {@link #COLUMN_REASON} when some possibly transient error occurred but we can't
      * resume the download.
      */
     public final static int ERROR_CANNOT_RESUME = 1008;
 
     /**
-     * Value of {@link #COLUMN_ERROR_CODE} when the requested destination file already exists (the
+     * Value of {@link #COLUMN_REASON} when the requested destination file already exists (the
      * download manager will not overwrite an existing file).
      */
     public final static int ERROR_FILE_ALREADY_EXISTS = 1009;
 
     /**
+     * Value of {@link #COLUMN_REASON} when the download is paused because some network error
+     * occurred and the download manager is waiting before retrying the request.
+     */
+    public final static int PAUSED_WAITING_TO_RETRY = 1;
+
+    /**
+     * Value of {@link #COLUMN_REASON} when the download is waiting for network connectivity to
+     * proceed.
+     */
+    public final static int PAUSED_WAITING_FOR_NETWORK = 2;
+
+    /**
+     * Value of {@link #COLUMN_REASON} when the download exceeds a size limit for downloads over
+     * the mobile network and the download manager is waiting for a Wi-Fi connection to proceed.
+     */
+    public final static int PAUSED_QUEUED_FOR_WIFI = 3;
+
+    /**
+     * Value of {@link #COLUMN_REASON} when the download is paused for some other reason.
+     */
+    public final static int PAUSED_UNKNOWN = 4;
+
+    /**
      * Broadcast intent action sent by the download manager when a download completes.
      */
     public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE";
 
     /**
-     * Broadcast intent action sent by the download manager when a running download notification is
-     * clicked.
+     * Broadcast intent action sent by the download manager when the user clicks on a running
+     * download, either from a system notification or from the downloads UI.
      */
     public final static String ACTION_NOTIFICATION_CLICKED =
             "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
@@ -236,7 +270,7 @@
         COLUMN_TOTAL_SIZE_BYTES,
         COLUMN_LOCAL_URI,
         COLUMN_STATUS,
-        COLUMN_ERROR_CODE,
+        COLUMN_REASON,
         COLUMN_BYTES_DOWNLOADED_SO_FAR,
         COLUMN_LAST_MODIFIED_TIMESTAMP
     };
@@ -258,7 +292,7 @@
     };
 
     private static final Set<String> LONG_COLUMNS = new HashSet<String>(
-            Arrays.asList(COLUMN_ID, COLUMN_TOTAL_SIZE_BYTES, COLUMN_STATUS, COLUMN_ERROR_CODE,
+            Arrays.asList(COLUMN_ID, COLUMN_TOTAL_SIZE_BYTES, COLUMN_STATUS, COLUMN_REASON,
                           COLUMN_BYTES_DOWNLOADED_SO_FAR, COLUMN_LAST_MODIFIED_TIMESTAMP));
 
     /**
@@ -383,7 +417,9 @@
         }
 
         /**
-         * Set the title of this download, to be displayed in notifications (if enabled)
+         * Set the title of this download, to be displayed in notifications (if enabled).  If no
+         * title is given, a default one will be assigned based on the download filename, once the
+         * download starts.
          * @return this object
          */
         public Request setTitle(CharSequence title) {
@@ -617,8 +653,10 @@
                     parts.add(statusClause("=", Downloads.STATUS_RUNNING));
                 }
                 if ((mStatusFlags & STATUS_PAUSED) != 0) {
-                    parts.add(statusClause("=", Downloads.STATUS_PENDING_PAUSED));
-                    parts.add(statusClause("=", Downloads.STATUS_RUNNING_PAUSED));
+                    parts.add(statusClause("=", Downloads.Impl.STATUS_PAUSED_BY_APP));
+                    parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_TO_RETRY));
+                    parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_FOR_NETWORK));
+                    parts.add(statusClause("=", Downloads.Impl.STATUS_QUEUED_FOR_WIFI));
                 }
                 if ((mStatusFlags & STATUS_SUCCESSFUL) != 0) {
                     parts.add(statusClause("=", Downloads.STATUS_SUCCESS));
@@ -891,7 +929,11 @@
 
             if (destinationType == Downloads.Impl.DESTINATION_EXTERNAL) {
                 // return stored destination for legacy external download
-                return Uri.fromFile(new File(getUnderlyingString(Downloads.Impl._DATA))).toString();
+                String localPath = getUnderlyingString(Downloads.Impl._DATA);
+                if (localPath == null) {
+                    return null;
+                }
+                return Uri.fromFile(new File(localPath)).toString();
             }
 
             // return content URI for cache download
@@ -914,8 +956,8 @@
             if (column.equals(COLUMN_STATUS)) {
                 return translateStatus((int) getUnderlyingLong(Downloads.COLUMN_STATUS));
             }
-            if (column.equals(COLUMN_ERROR_CODE)) {
-                return translateErrorCode((int) getUnderlyingLong(Downloads.COLUMN_STATUS));
+            if (column.equals(COLUMN_REASON)) {
+                return getReason((int) getUnderlyingLong(Downloads.COLUMN_STATUS));
             }
             if (column.equals(COLUMN_BYTES_DOWNLOADED_SO_FAR)) {
                 return getUnderlyingLong(Downloads.COLUMN_CURRENT_BYTES);
@@ -924,10 +966,36 @@
             return getUnderlyingLong(Downloads.COLUMN_LAST_MODIFICATION);
         }
 
-        private long translateErrorCode(int status) {
-            if (translateStatus(status) != STATUS_FAILED) {
-                return 0; // arbitrary value when status is not an error
+        private long getReason(int status) {
+            switch (translateStatus(status)) {
+                case STATUS_FAILED:
+                    return getErrorCode(status);
+
+                case STATUS_PAUSED:
+                    return getPausedReason(status);
+
+                default:
+                    return 0; // arbitrary value when status is not an error
             }
+        }
+
+        private long getPausedReason(int status) {
+            switch (status) {
+                case Downloads.Impl.STATUS_WAITING_TO_RETRY:
+                    return PAUSED_WAITING_TO_RETRY;
+
+                case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
+                    return PAUSED_WAITING_FOR_NETWORK;
+
+                case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
+                    return PAUSED_QUEUED_FOR_WIFI;
+
+                default:
+                    return PAUSED_UNKNOWN;
+            }
+        }
+
+        private long getErrorCode(int status) {
             if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS)
                     || (500 <= status && status < 600)) {
                 // HTTP status code
@@ -973,7 +1041,7 @@
             return super.getString(super.getColumnIndex(column));
         }
 
-        private long translateStatus(int status) {
+        private int translateStatus(int status) {
             switch (status) {
                 case Downloads.STATUS_PENDING:
                     return STATUS_PENDING;
@@ -981,8 +1049,10 @@
                 case Downloads.STATUS_RUNNING:
                     return STATUS_RUNNING;
 
-                case Downloads.STATUS_PENDING_PAUSED:
-                case Downloads.STATUS_RUNNING_PAUSED:
+                case Downloads.Impl.STATUS_PAUSED_BY_APP:
+                case Downloads.Impl.STATUS_WAITING_TO_RETRY:
+                case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
+                case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
                     return STATUS_PAUSED;
 
                 case Downloads.STATUS_SUCCESS:
diff --git a/core/java/android/bluetooth/AtCommandResult.java b/core/java/android/bluetooth/AtCommandResult.java
index 638be2d..375a6dd 100644
--- a/core/java/android/bluetooth/AtCommandResult.java
+++ b/core/java/android/bluetooth/AtCommandResult.java
@@ -16,8 +16,6 @@
 
 package android.bluetooth;
 
-import java.util.*;
-
 /**
  * The result of execution of an single AT command.<p>
  *
diff --git a/core/java/android/bluetooth/AtParser.java b/core/java/android/bluetooth/AtParser.java
index 1ea3150..328fb2b 100644
--- a/core/java/android/bluetooth/AtParser.java
+++ b/core/java/android/bluetooth/AtParser.java
@@ -16,16 +16,13 @@
 
 package android.bluetooth;
 
-import android.bluetooth.AtCommandHandler;
-import android.bluetooth.AtCommandResult;
-
 import java.util.*;
 
 /**
  * An AT (Hayes command) Parser based on (a subset of) the ITU-T V.250 standard.
  * <p>
  *
- * Conforment with the subset of V.250 required for implementation of the
+ * Conformant with the subset of V.250 required for implementation of the
  * Bluetooth Headset and Handsfree Profiles, as per Bluetooth SIP
  * specifications. Also implements some V.250 features not required by
  * Bluetooth - such as chained commands.<p>
@@ -48,7 +45,7 @@
  * are no arguments for get commands.
  * <li>Set Command. For example "AT+VGM=14". The command name is "VGM", and
  * there is a single integer argument in this case. In the general case then
- * can be zero or more arguments (comma deliminated) each of integer or string
+ * can be zero or more arguments (comma delimited) each of integer or string
  * form.
  * <li>Test Command. For example "AT+VGM=?. No arguments.
  * </ul>
@@ -60,7 +57,7 @@
  * headset/handsfree use this is acceptable, because they only use the basic
  * commands ATA and ATD, which are not allowed to be chained. For general V.250
  * use we would need to improve this class to allow Basic command chaining -
- * however its tricky to get right becuase there is no deliminator for Basic
+ * however it's tricky to get right because there is no delimiter for Basic
  * command chaining.<p>
  *
  * Extended commands can be chained. For example:<p>
@@ -71,7 +68,7 @@
  * AT+CIMI
  * Except that only one final result code is return (although several
  * intermediate responses may be returned), and as soon as one command in the
- * chain fails the rest are abandonded.<p>
+ * chain fails the rest are abandoned.<p>
  *
  * Handlers are registered by there command name via register(Char c, ...) or
  * register(String s, ...). Handlers for Basic command should be registered by
@@ -80,7 +77,7 @@
  *
  * Refer to:<ul>
  * <li>ITU-T Recommendation V.250
- * <li>ETSI TS 127.007  (AT Comannd set for User Equipment, 3GPP TS 27.007)
+ * <li>ETSI TS 127.007  (AT Command set for User Equipment, 3GPP TS 27.007)
  * <li>Bluetooth Headset Profile Spec (K6)
  * <li>Bluetooth Handsfree Profile Spec (HFP 1.5)
  * </ul>
@@ -188,7 +185,7 @@
     }
 
     /**
-     * Break an argument string into individual arguments (comma deliminated).
+     * Break an argument string into individual arguments (comma delimited).
      * Integer arguments are turned into Integer objects. Otherwise a String
      * object is used.
      */
@@ -212,7 +209,7 @@
     }
 
     /**
-     * Return the index of the end of character after the last characeter in
+     * Return the index of the end of character after the last character in
      * the extended command name. Uses the V.250 spec for allowed command
      * names.
      */
@@ -244,7 +241,7 @@
      * Processes an incoming AT command line.<p>
      * This method will invoke zero or one command handler methods for each
      * command in the command line.<p>
-     * @param raw_input The AT input, without EOL deliminator (e.g. <CR>).
+     * @param raw_input The AT input, without EOL delimiter (e.g. <CR>).
      * @return          Result object for this command line. This can be
      *                  converted to a String[] response with toStrings().
      */
@@ -297,8 +294,8 @@
 
             if (c == '+') {
                 // Option 2: Extended Command
-                // Search for first non-name character. Shortcircuit if we dont
-                // handle this command name.
+                // Search for first non-name character. Short-circuit if
+                // we don't handle this command name.
                 int i = findEndExtendedName(input, index + 1);
                 String commandName = input.substring(index, i);
                 if (!mExtHandlers.containsKey(commandName)) {
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 21a4bd6..c66b2de 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -341,7 +341,7 @@
     private static final int ADDRESS_LENGTH = 17;
 
     /**
-     * Lazyily initialized singleton. Guaranteed final after first object
+     * Lazily initialized singleton. Guaranteed final after first object
      * constructed.
      */
     private static BluetoothAdapter sAdapter;
@@ -466,7 +466,7 @@
      * user action to turn off Bluetooth.
      * <p>This gracefully shuts down all Bluetooth connections, stops Bluetooth
      * system services, and powers down the underlying Bluetooth hardware.
-     * <p class="caution"><strong>Bluetooth should never be disbled without
+     * <p class="caution"><strong>Bluetooth should never be disabled without
      * direct user consent</strong>. The {@link #disable()} method is
      * provided only for applications that include a user interface for changing
      * system settings, such as a "power manager" app.</p>
@@ -932,8 +932,8 @@
     public Pair<byte[], byte[]> readOutOfBandData() {
         if (getState() != STATE_ON) return null;
         try {
-            byte[] hash = new byte[16];
-            byte[] randomizer = new byte[16];
+            byte[] hash;
+            byte[] randomizer;
 
             byte[] ret = mService.readOutOfBandData();
 
diff --git a/core/java/android/bluetooth/BluetoothAudioGateway.java b/core/java/android/bluetooth/BluetoothAudioGateway.java
index bc32060..9351393 100644
--- a/core/java/android/bluetooth/BluetoothAudioGateway.java
+++ b/core/java/android/bluetooth/BluetoothAudioGateway.java
@@ -23,7 +23,7 @@
 import android.util.Log;
 
 /**
- * Listen's for incoming RFCOMM connection for the headset / handsfree service.
+ * Listens for incoming RFCOMM connection for the headset / handsfree service.
  *
  * TODO: Use the new generic BluetoothSocket class instead of this legacy code
  *
diff --git a/core/java/android/bluetooth/BluetoothClass.java b/core/java/android/bluetooth/BluetoothClass.java
index c8381c9..e604e6b 100644
--- a/core/java/android/bluetooth/BluetoothClass.java
+++ b/core/java/android/bluetooth/BluetoothClass.java
@@ -34,8 +34,8 @@
  * Bluetooth profiles or services are actually supported by a device. Accurate
  * service discovery is done through SDP requests, which are automatically
  * performed when creating an RFCOMM socket with {@link
- * BluetoothDevice#createRfcommSocketToServiceRecord(UUID)} and {@link
- * BluetoothAdapter#listenUsingRfcommWithServiceRecord(String,UUID)}</p>
+ * BluetoothDevice#createRfcommSocketToServiceRecord} and {@link
+ * BluetoothAdapter#listenUsingRfcommWithServiceRecord}</p>
  *
  * <p>Use {@link BluetoothDevice#getBluetoothClass} to retrieve the class for
  * a remote device.
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index e577ec4..ada3c24 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -32,7 +32,7 @@
 
 /**
  * Represents a remote Bluetooth device. A {@link BluetoothDevice} lets you
- * create a connection with the repective device or query information about
+ * create a connection with the respective device or query information about
  * it, such as the name, address, class, and bonding state.
  *
  * <p>This class is really just a thin wrapper for a Bluetooth hardware
@@ -48,7 +48,7 @@
  * {@link BluetoothAdapter}) or get one from the set of bonded devices
  * returned by {@link BluetoothAdapter#getBondedDevices()
  * BluetoothAdapter.getBondedDevices()}. You can then open a
- * {@link BluetoothSocket} for communciation with the remote device, using
+ * {@link BluetoothSocket} for communication with the remote device, using
  * {@link #createRfcommSocketToServiceRecord(UUID)}.
  *
  * <p class="note"><strong>Note:</strong>
@@ -226,8 +226,8 @@
      * <p>A shared link keys exists locally for the remote device, so
      * communication can be authenticated and encrypted.
      * <p><i>Being bonded (paired) with a remote device does not necessarily
-     * mean the device is currently connected. It just means that the ponding
-     * procedure was compeleted at some earlier time, and the link key is still
+     * mean the device is currently connected. It just means that the pending
+     * procedure was completed at some earlier time, and the link key is still
      * stored locally, ready to use on the next connection.
      * </i>
      */
@@ -283,7 +283,7 @@
      * not respond to pin request in time
      * @hide */
     public static final int UNBOND_REASON_AUTH_FAILED = 1;
-    /** A bond attempt failed because the other side explicilty rejected
+    /** A bond attempt failed because the other side explicitly rejected
      * bonding
      * @hide */
     public static final int UNBOND_REASON_AUTH_REJECTED = 2;
@@ -515,7 +515,7 @@
      * Cancel an in-progress bonding request started with {@link #createBond}.
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
      *
-     * @return true on sucess, false on error
+     * @return true on success, false on error
      * @hide
      */
     public boolean cancelBondProcess() {
@@ -532,7 +532,7 @@
      * authentication and encryption.
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
      *
-     * @return true on sucess, false on error
+     * @return true on success, false on error
      * @hide
      */
     public boolean removeBond() {
@@ -617,7 +617,7 @@
       *  with the UUIDs supported by the remote end. If there is an error
       *  in getting the SDP records or if the process takes a long time,
       *  an Intent is sent with the UUIDs that is currently present in the
-      *  cache. Clients should use the {@link getUuids} to get UUIDs
+      *  cache. Clients should use the {@link #getUuids} to get UUIDs
       *  is SDP is not to be performed.
       *
       *  @return False if the sanity check fails, True if the process
@@ -693,7 +693,7 @@
      * outgoing connection to this remote device on given channel.
      * <p>The remote device will be authenticated and communication on this
      * socket will be encrypted.
-     * <p>Use {@link BluetoothSocket#connect} to intiate the outgoing
+     * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
      * connection.
      * <p>Valid RFCOMM channels are in range 1 to 30.
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
@@ -715,7 +715,7 @@
      * <p>This is designed to be used with {@link
      * BluetoothAdapter#listenUsingRfcommWithServiceRecord} for peer-peer
      * Bluetooth applications.
-     * <p>Use {@link BluetoothSocket#connect} to intiate the outgoing
+     * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
      * connection. This will also perform an SDP lookup of the given uuid to
      * determine which channel to connect to.
      * <p>The remote device will be authenticated and communication on this
@@ -772,9 +772,9 @@
     /**
      * Check that a pin is valid and convert to byte array.
      *
-     * Bluetooth pin's are 1 to 16 bytes of UTF8 characters.
+     * Bluetooth pin's are 1 to 16 bytes of UTF-8 characters.
      * @param pin pin as java String
-     * @return the pin code as a UTF8 byte array, or null if it is an invalid
+     * @return the pin code as a UTF-8 byte array, or null if it is an invalid
      *         Bluetooth pin.
      * @hide
      */
@@ -784,9 +784,9 @@
         }
         byte[] pinBytes;
         try {
-            pinBytes = pin.getBytes("UTF8");
+            pinBytes = pin.getBytes("UTF-8");
         } catch (UnsupportedEncodingException uee) {
-            Log.e(TAG, "UTF8 not supported?!?");  // this should not happen
+            Log.e(TAG, "UTF-8 not supported?!?");  // this should not happen
             return null;
         }
         if (pinBytes.length <= 0 || pinBytes.length > 16) {
diff --git a/core/java/android/bluetooth/BluetoothDevicePicker.java b/core/java/android/bluetooth/BluetoothDevicePicker.java
index 7415721..c794be2 100644
--- a/core/java/android/bluetooth/BluetoothDevicePicker.java
+++ b/core/java/android/bluetooth/BluetoothDevicePicker.java
@@ -36,7 +36,8 @@
 
     /**
      * Broadcast when one BT device is selected from BT device picker screen.
-     * Selected BT device address is contained in extra string {@link BluetoothIntent}
+     * Selected {@link BluetoothDevice} is returned in extra data named
+     * {@link BluetoothDevice#EXTRA_DEVICE}.
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_DEVICE_SELECTED =
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index b48f48e..4be077c 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -197,7 +197,7 @@
 
     /**
      * Disconnects the current Pbap client (PCE). Currently this call blocks,
-     * it may soon be made asynchornous. Returns false if this proxy object is
+     * it may soon be made asynchronous. Returns false if this proxy object is
      * not currently connected to the Pbap service.
      */
     public boolean disconnect() {
diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java
index c9c6c0a..83e59e2 100644
--- a/core/java/android/bluetooth/BluetoothServerSocket.java
+++ b/core/java/android/bluetooth/BluetoothServerSocket.java
@@ -29,14 +29,14 @@
  * side, use a {@link BluetoothServerSocket} to create a listening server
  * socket. When a connection is accepted by the {@link BluetoothServerSocket},
  * it will return a new {@link BluetoothSocket} to manage the connection.
- * On the client side, use a single {@link BluetoothSocket} to both intiate
+ * On the client side, use a single {@link BluetoothSocket} to both initiate
  * an outgoing connection and to manage the connection.
  *
  * <p>The most common type of Bluetooth socket is RFCOMM, which is the type
  * supported by the Android APIs. RFCOMM is a connection-oriented, streaming
  * transport over Bluetooth. It is also known as the Serial Port Profile (SPP).
  *
- * <p>To create a listenting {@link BluetoothServerSocket} that's ready for
+ * <p>To create a listening {@link BluetoothServerSocket} that's ready for
  * incoming connections, use
  * {@link BluetoothAdapter#listenUsingRfcommWithServiceRecord
  * BluetoothAdapter.listenUsingRfcommWithServiceRecord()}. Then call
@@ -70,7 +70,7 @@
      * @param encrypt require the connection to be encrypted
      * @param port    remote port
      * @throws IOException On error, for example Bluetooth not available, or
-     *                     insufficient priveleges
+     *                     insufficient privileges
      */
     /*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, int port)
             throws IOException {
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index ad03399..719d730 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -35,7 +35,7 @@
  * side, use a {@link BluetoothServerSocket} to create a listening server
  * socket. When a connection is accepted by the {@link BluetoothServerSocket},
  * it will return a new {@link BluetoothSocket} to manage the connection.
- * On the client side, use a single {@link BluetoothSocket} to both intiate
+ * On the client side, use a single {@link BluetoothSocket} to both initiate
  * an outgoing connection and to manage the connection.
  *
  * <p>The most common type of Bluetooth socket is RFCOMM, which is the type
@@ -113,7 +113,7 @@
      * @param port    remote port
      * @param uuid    SDP uuid
      * @throws IOException On error, for example Bluetooth not available, or
-     *                     insufficient priveleges
+     *                     insufficient privileges
      */
     /*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
             BluetoothDevice device, int port, ParcelUuid uuid) throws IOException {
@@ -158,7 +158,7 @@
      * @param address remote device that this socket can connect to
      * @param port    remote port
      * @throws IOException On error, for example Bluetooth not available, or
-     *                     insufficient priveleges
+     *                     insufficient privileges
      */
     private BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address,
             int port) throws IOException {
@@ -226,7 +226,7 @@
         }
 
         // all native calls are guaranteed to immediately return after
-        // abortNative(), so this lock should immediatley acquire
+        // abortNative(), so this lock should immediately acquire
         mLock.writeLock().lock();
         try {
             mClosed = true;
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 8ef639b..eaf1e33 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1730,6 +1730,11 @@
 
                 XmlUtils.skipCurrentTag(parser);
 
+            } else if (tagName.equals("uses-package")) {
+                // Dependencies for app installers; we don't currently try to
+                // enforce this.
+                XmlUtils.skipCurrentTag(parser);
+
             } else {
                 if (!RIGID_PARSER) {
                     Log.w(TAG, "Unknown element under <application>: " + tagName
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index d0ba590..406b091 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -18,6 +18,7 @@
 
 import android.content.pm.ApplicationInfo;
 import android.graphics.Canvas;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.util.DisplayMetrics;
@@ -363,6 +364,17 @@
         }
 
         /**
+         * Translate a Point in screen coordinates into the app window's coordinates.
+         */
+        public void translatePointInScreenToAppWindow(PointF point) {
+            final float scale = applicationInvertedScale;
+            if (scale != 1.0f) {
+                point.x *= scale;
+                point.y *= scale;
+            }
+        }
+
+        /**
          * Translate the location of the sub window.
          * @param params
          */
diff --git a/core/java/android/content/res/ObbInfo.java b/core/java/android/content/res/ObbInfo.java
index 7b962e5..5d6ed44 100644
--- a/core/java/android/content/res/ObbInfo.java
+++ b/core/java/android/content/res/ObbInfo.java
@@ -29,6 +29,11 @@
     public static final int OBB_OVERLAY = 1 << 0;
 
     /**
+     * The canonical filename of the OBB.
+     */
+    public String filename;
+
+    /**
      * The name of the package to which the OBB file belongs.
      */
     public String packageName;
@@ -66,6 +71,7 @@
     }
 
     public void writeToParcel(Parcel dest, int parcelableFlags) {
+        dest.writeString(filename);
         dest.writeString(packageName);
         dest.writeInt(version);
         dest.writeInt(flags);
@@ -83,6 +89,7 @@
     };
 
     private ObbInfo(Parcel source) {
+        filename = source.readString();
         packageName = source.readString();
         version = source.readInt();
         flags = source.readInt();
diff --git a/core/java/android/content/res/ObbScanner.java b/core/java/android/content/res/ObbScanner.java
index 572b75e..1b38eea 100644
--- a/core/java/android/content/res/ObbScanner.java
+++ b/core/java/android/content/res/ObbScanner.java
@@ -45,9 +45,14 @@
             throw new IllegalArgumentException("OBB file does not exist: " + filePath);
         }
 
+        /*
+         * XXX This will fail to find the real canonical path if bind mounts are
+         * used, but we don't use any bind mounts right now.
+         */
         final String canonicalFilePath = obbFile.getCanonicalPath();
 
         ObbInfo obbInfo = new ObbInfo();
+        obbInfo.filename = canonicalFilePath;
         getObbInfo_native(canonicalFilePath, obbInfo);
 
         return obbInfo;
diff --git a/core/java/android/database/sqlite/SQLiteCompiledSql.java b/core/java/android/database/sqlite/SQLiteCompiledSql.java
index 6ed1a90..a7ad757 100644
--- a/core/java/android/database/sqlite/SQLiteCompiledSql.java
+++ b/core/java/android/database/sqlite/SQLiteCompiledSql.java
@@ -16,6 +16,7 @@
 
 package android.database.sqlite;
 
+import android.os.StrictMode;
 import android.util.Log;
 
 /**
@@ -106,11 +107,13 @@
             // but if the database itself is not closed and is GC'ed, then
             // all sub-objects attached to the database could end up getting GC'ed too.
             // in that case, don't print any warning.
-            if (mInUse) {
+            if (mInUse && StrictMode.vmSqliteObjectLeaksEnabled()) {
                 int len = mSqlStmt.length();
-                Log.w(TAG, "Releasing statement in a finalizer. Please ensure " +
-                        "that you explicitly call close() on your cursor: " +
-                        mSqlStmt.substring(0, (len > 100) ? 100 : len), mStackTrace);
+                StrictMode.onSqliteObjectLeaked(
+                    "Releasing statement in a finalizer. Please ensure " +
+                    "that you explicitly call close() on your cursor: " +
+                    mSqlStmt.substring(0, (len > 100) ? 100 : len),
+                    mStackTrace);
             }
             releaseSqlStatement();
         } finally {
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index fa7763d..89e8ab7 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -25,6 +25,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
+import android.os.StrictMode;
 import android.util.Config;
 import android.util.Log;
 
@@ -505,11 +506,14 @@
         try {
             // if the cursor hasn't been closed yet, close it first
             if (mWindow != null) {
-                int len = mQuery.mSql.length();
-                Log.e(TAG, "Finalizing a Cursor that has not been deactivated or closed. " +
+                if (StrictMode.vmSqliteObjectLeaksEnabled()) {
+                    int len = mQuery.mSql.length();
+                    StrictMode.onSqliteObjectLeaked(
+                        "Finalizing a Cursor that has not been deactivated or closed. " +
                         "database = " + mQuery.mDatabase.getPath() + ", table = " + mEditTable +
                         ", query = " + mQuery.mSql.substring(0, (len > 100) ? 100 : len),
                         mStackTrace);
+                }
                 close();
                 SQLiteDebug.notifyActiveCursorFinalized();
             } else {
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 0d8228c..7b930d5 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -181,6 +181,7 @@
          * value should be 90.
          *
          * @see #setDisplayOrientation(int)
+         * @see #setRotation(int)
          */
         public int orientation;
     };
@@ -1716,23 +1717,46 @@
         }
 
         /**
-         * Sets the orientation of the device in degrees. For example, suppose
-         * the natural position of the device is landscape. If the user takes a
-         * picture in landscape mode in 2048x1536 resolution, the rotation
-         * should be set to 0. If the user rotates the phone 90 degrees
-         * clockwise, the rotation should be set to 90. Applications can use
-         * {@link android.view.OrientationEventListener} to set this parameter.
+         * Sets the rotation angle in degrees relative to the orientation of
+         * the camera. This affects the pictures returned from JPEG {@link
+         * PictureCallback}. The camera driver may set orientation in the
+         * EXIF header without rotating the picture. Or the driver may rotate
+         * the picture and the EXIF thumbnail. If the Jpeg picture is rotated,
+         * the orientation in the EXIF header will be missing or 1 (row #0 is
+         * top and column #0 is left side).
          *
-         * The camera driver may set orientation in the EXIF header without
-         * rotating the picture. Or the driver may rotate the picture and
-         * the EXIF thumbnail. If the Jpeg picture is rotated, the orientation
-         * in the EXIF header will be missing or 1 (row #0 is top and column #0
-         * is left side).
+         * If appplications want to rotate the picture to match the
+         * orientation of what users see, apps should use {@link
+         * android.view.OrientationEventListener} and {@link CameraInfo}.
+         * The value from OrientationEventListener is relative to the natural
+         * orientation of the device. CameraInfo.mOrientation is the angle
+         * between camera orientation and natural device orientation. The sum
+         * of the two is the angle for rotation.
          *
-         * @param rotation The orientation of the device in degrees. Rotation
-         *                 can only be 0, 90, 180 or 270.
+         * For example, suppose the natural orientation of the device is
+         * portrait. The device is rotated 270 degrees clockwise, so the device
+         * orientation is 270. Suppose the camera sensor is mounted in landscape
+         * and the top side of the camera sensor is aligned with the right edge
+         * of the display in natural orientation. So the camera orientation is
+         * 90. The rotation should be set to 0 (270 + 90).
+         *
+         * The reference code is as follows.
+         *
+         * public void public void onOrientationChanged(int orientation) {
+         *     if (orientation == ORIENTATION_UNKNOWN) return;
+         *     android.hardware.Camera.CameraInfo info =
+         *            new android.hardware.Camera.CameraInfo();
+         *     android.hardware.Camera.getCameraInfo(cameraId, info);
+         *     orientation = (orientation + 45) / 90 * 90;
+         *     mParameters.setRotation((orientation + info.mOrientation) % 360);
+         * }
+         *
+         * @param rotation The rotation angle in degrees relative to the
+         *                 orientation of the camera. Rotation can only be 0,
+         *                 90, 180 or 270.
          * @throws IllegalArgumentException if rotation value is invalid.
          * @see android.view.OrientationEventListener
+         * @see #getCameraInfo(int, CameraInfo)
          */
         public void setRotation(int rotation) {
             if (rotation == 0 || rotation == 90 || rotation == 180
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index 3df8ec0..ad7289d 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -45,7 +45,7 @@
 public class MobileDataStateTracker implements NetworkStateTracker {
 
     private static final String TAG = "MobileDataStateTracker";
-    private static final boolean DBG = true;
+    private static final boolean DBG = false;
 
     private Phone.DataState mMobileDataState;
     private ITelephony mPhoneService;
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index e56e257..6d19f41 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -68,6 +68,7 @@
     private static File RECOVERY_DIR = new File("/cache/recovery");
     private static File COMMAND_FILE = new File(RECOVERY_DIR, "command");
     private static File LOG_FILE = new File(RECOVERY_DIR, "log");
+    private static String LAST_LOG_FILENAME = "last_log";
 
     // Length limits for reading files.
     private static int LOG_FILE_MAX_LENGTH = 64 * 1024;
@@ -399,9 +400,10 @@
             Log.e(TAG, "Error reading recovery log", e);
         }
 
-        // Delete everything in RECOVERY_DIR
+        // Delete everything in RECOVERY_DIR except LAST_LOG_FILENAME
         String[] names = RECOVERY_DIR.list();
         for (int i = 0; names != null && i < names.length; i++) {
+            if (names[i].equals(LAST_LOG_FILENAME)) continue;
             File f = new File(RECOVERY_DIR, names[i]);
             if (!f.delete()) {
                 Log.e(TAG, "Can't delete: " + f);
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 3ddaad9..9494a06 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -30,8 +30,9 @@
 import java.util.HashMap;
 
 /**
- * <p>StrictMode is a developer tool which lets you impose stricter
- * rules under which your application runs.
+ * <p>StrictMode is a developer tool which detects things you might be
+ * doing by accident and brings them to your attention so you can fix
+ * them.
  *
  * <p>StrictMode is most commonly used to catch accidental disk or
  * network access on the application's main thread, where UI
@@ -55,24 +56,33 @@
  * <pre>
  * public void onCreate() {
  *     if (DEVELOPER_MODE) {
- *         StrictMode.setThreadPolicy(StrictMode.DISALLOW_DISK_WRITE |
- *                 StrictMode.DISALLOW_DISK_READ |
- *                 StrictMode.DISALLOW_NETWORK |
- *                 StrictMode.PENALTY_LOG);
+ *         StrictMode.setThreadPolicy(new {@link ThreadPolicy.Builder StrictMode.ThreadPolicy.Builder}()
+ *                 .detectDiskReads()
+ *                 .detectDiskWrites()
+ *                 .detectNetwork()   // or .detectAll() for all detectable problems
+ *                 .penaltyLog()
+ *                 .build());
+ *         StrictMode.setVmPolicy(new {@link VmPolicy.Builder StrictMode.VmPolicy.Builder}()
+ *                 .detectLeakedSqlLiteCursors()
+ *                 .penaltyLog()
+ *                 .penaltyDeath()
+ *                 .build());
  *     }
  *     super.onCreate();
  * }
  * </pre>
  *
- * <p>Then you can watch the output of <code>adb logcat</code> while you
- * use your application.
+ * <p>You can decide what should happen when a violation is detected.
+ * For example, using {@link ThreadPolicy.Builder#penaltyLog} you can
+ * watch the output of <code>adb logcat</code> while you use your
+ * application to see the violations as they happen.
  *
  * <p>If you find violations that you feel are problematic, there are
  * a variety of tools to help solve them: threads, {@link android.os.Handler},
  * {@link android.os.AsyncTask}, {@link android.app.IntentService}, etc.
  * But don't feel compelled to fix everything that StrictMode finds.  In particular,
- * a lot of disk accesses are often necessary during the normal activity lifecycle.  Use
- * StrictMode to find things you did on accident.  Network requests on the UI thread
+ * many cases of disk access are often necessary during the normal activity lifecycle.  Use
+ * StrictMode to find things you did by accident.  Network requests on the UI thread
  * are almost always a problem, though.
  *
  * <p class="note">StrictMode is not a security mechanism and is not
@@ -94,55 +104,50 @@
     // Only show an annoying dialog at most every 30 seconds
     private static final long MIN_DIALOG_INTERVAL_MS = 30000;
 
-    private StrictMode() {}
+    // Thread-policy:
 
     /**
-     * Flag for {@link #setThreadPolicy} to signal that you don't intend for this
-     * thread to write to disk.
+     * @hide
      */
-    public static final int DISALLOW_DISK_WRITE = 0x01;
+    public static final int DETECT_DISK_WRITE = 0x01;  // for ThreadPolicy
 
     /**
-     * Flag for {@link #setThreadPolicy} to signal that you don't intend for this
-     * thread to read from disk.
+      * @hide
      */
-    public static final int DISALLOW_DISK_READ = 0x02;
+    public static final int DETECT_DISK_READ = 0x02;  // for ThreadPolicy
 
     /**
-     * Flag for {@link #setThreadPolicy} to signal that you don't intend for this
-     * thread to access the network.
+     * @hide
      */
-    public static final int DISALLOW_NETWORK = 0x04;
+    public static final int DETECT_NETWORK = 0x04;  // for ThreadPolicy
 
-    /** @hide */
-    public static final int DISALLOW_MASK =
-            DISALLOW_DISK_WRITE | DISALLOW_DISK_READ | DISALLOW_NETWORK;
+    // Process-policy:
 
     /**
-     * Penalty flag for {@link #setThreadPolicy} to log violations to
-     * the system log, visible with <code>adb logcat</code>.
+     * Note, a "VM_" bit, not thread.
+     * @hide
+     */
+    public static final int DETECT_VM_CURSOR_LEAKS = 0x200;  // for ProcessPolicy
+
+    /**
+     * @hide
      */
     public static final int PENALTY_LOG = 0x10;  // normal android.util.Log
 
+    // Used for both process and thread policy:
+
     /**
-     * Penalty flag for {@link #setThreadPolicy} to show an annoying
-     * dialog to the developer, rate-limited to be only a little
-     * annoying.
+     * @hide
      */
     public static final int PENALTY_DIALOG = 0x20;
 
     /**
-     * Penalty flag for {@link #setThreadPolicy} to crash hard if
-     * policy is violated.
+     * @hide
      */
     public static final int PENALTY_DEATH = 0x40;
 
     /**
-     * Penalty flag for {@link #setThreadPolicy} to log a stacktrace
-     * and timing data to the
-     * {@link android.os.DropBoxManager DropBox} on policy violation.
-     * Intended mostly for platform integrators doing beta user field
-     * data collection.
+     * @hide
      */
     public static final int PENALTY_DROPBOX = 0x80;
 
@@ -159,10 +164,321 @@
      */
     public static final int PENALTY_GATHER = 0x100;
 
-    /** @hide */
-    public static final int PENALTY_MASK =
-            PENALTY_LOG | PENALTY_DIALOG |
-            PENALTY_DROPBOX | PENALTY_DEATH;
+    /**
+     * The current VmPolicy in effect.
+     */
+    private static volatile int sVmPolicyMask = 0;
+
+    private StrictMode() {}
+
+    /**
+     * {@link StrictMode} policy applied to a certain thread.
+     *
+     * <p>The policy is enabled by {@link #setThreadPolicy}.  The current policy
+     * can be retrieved with {@link #getThreadPolicy}.
+     *
+     * <p>Note that multiple penalties may be provided and they're run
+     * in order from least to most severe (logging before process
+     * death, for example).  There's currently no mechanism to choose
+     * different penalties for different detected actions.
+     */
+    public static final class ThreadPolicy {
+        /**
+         * The default, lax policy which doesn't catch anything.
+         */
+        public static final ThreadPolicy LAX = new ThreadPolicy(0);
+
+        final int mask;
+
+        private ThreadPolicy(int mask) {
+            this.mask = mask;
+        }
+
+        @Override
+        public String toString() {
+            return "[StrictMode.ThreadPolicy; mask=" + mask + "]";
+        }
+
+        /**
+         * Creates ThreadPolicy instances.  Methods whose names start
+         * with {@code detect} specify what problems we should look
+         * for.  Methods whose names start with {@code penalty} specify what
+         * we should do when we detect a problem.
+         *
+         * <p>You can call as many {@code detect} and {@code penalty}
+         * methods as you like. Currently order is insignificant: all
+         * penalties apply to all detected problems.
+         *
+         * <p>For example, detect everything and log anything that's found:
+         * <pre>
+         * StrictMode.VmPolicy policy = new StrictMode.VmPolicy.Builder()
+         *     .detectAll()
+         *     .penaltyLog()
+         *     .build();
+         * StrictMode.setVmPolicy(policy);
+         * </pre>
+         */
+        public static final class Builder {
+            private int mMask = 0;
+
+            /**
+             * Create a Builder that detects nothing and has no
+             * violations.  (but note that {@link #build} will default
+             * to enabling {@link #penaltyLog} if no other penalties
+             * are specified)
+             */
+            public Builder() {
+                mMask = 0;
+            }
+
+            /**
+             * Initialize a Builder from an existing ThreadPolicy.
+             */
+            public Builder(ThreadPolicy policy) {
+                mMask = policy.mask;
+            }
+
+            /**
+             * Detect everything that's potentially suspect.
+             *
+             * <p>As of the Gingerbread release this includes network and
+             * disk operations but will likely expand in future releases.
+             */
+            public Builder detectAll() {
+                return enable(DETECT_DISK_WRITE | DETECT_DISK_READ | DETECT_NETWORK);
+            }
+
+            /**
+             * Disable the detection of everything.
+             */
+            public Builder permitAll() {
+                return disable(DETECT_DISK_WRITE | DETECT_DISK_READ | DETECT_NETWORK);
+            }
+
+            /**
+             * Enable detection of network operations.
+             */
+            public Builder detectNetwork() {
+                return enable(DETECT_NETWORK);
+            }
+
+            /**
+             * Disable detection of network operations.
+             */
+            public Builder permitNetwork() {
+                return disable(DETECT_NETWORK);
+            }
+
+            /**
+             * Enable detection of disk reads.
+             */
+            public Builder detectDiskReads() {
+                return enable(DETECT_DISK_READ);
+            }
+
+            /**
+             * Disable detection of disk reads.
+             */
+            public Builder permitDiskReads() {
+                return disable(DETECT_DISK_READ);
+            }
+
+            /**
+             * Enable detection of disk writes.
+             */
+            public Builder detectDiskWrites() {
+                return enable(DETECT_DISK_WRITE);
+            }
+
+            /**
+             * Disable detection of disk writes.
+             */
+            public Builder permitDiskWrites() {
+                return disable(DETECT_DISK_WRITE);
+            }
+
+            /**
+             * Show an annoying dialog to the developer on detected
+             * violations, rate-limited to be only a little annoying.
+             */
+            public Builder penaltyDialog() {
+                return enable(PENALTY_DIALOG);
+            }
+
+            /**
+             * Crash the whole process on violation.  This penalty runs at
+             * the end of all enabled penalties so you'll still get
+             * see logging or other violations before the process dies.
+             */
+            public Builder penaltyDeath() {
+                return enable(PENALTY_DEATH);
+            }
+
+            /**
+             * Log detected violations to the system log.
+             */
+            public Builder penaltyLog() {
+                return enable(PENALTY_LOG);
+            }
+
+            /**
+             * Enable detected violations log a stacktrace and timing data
+             * to the {@link android.os.DropBoxManager DropBox} on policy
+             * violation.  Intended mostly for platform integrators doing
+             * beta user field data collection.
+             */
+            public Builder penaltyDropBox() {
+                return enable(PENALTY_DROPBOX);
+            }
+
+            private Builder enable(int bit) {
+                mMask |= bit;
+                return this;
+            }
+
+            private Builder disable(int bit) {
+                mMask &= ~bit;
+                return this;
+            }
+
+            /**
+             * Construct the ThreadPolicy instance.
+             *
+             * <p>Note: if no penalties are enabled before calling
+             * <code>build</code>, {@link #penaltyLog} is implicitly
+             * set.
+             */
+            public ThreadPolicy build() {
+                // If there are detection bits set but no violation bits
+                // set, enable simple logging.
+                if (mMask != 0 &&
+                    (mMask & (PENALTY_DEATH | PENALTY_LOG |
+                              PENALTY_DROPBOX | PENALTY_DIALOG)) == 0) {
+                    penaltyLog();
+                }
+                return new ThreadPolicy(mMask);
+            }
+        }
+    }
+
+    /**
+     * {@link StrictMode} policy applied to all threads in the virtual machine's process.
+     *
+     * <p>The policy is enabled by {@link #setVmPolicy}.
+     */
+    public static final class VmPolicy {
+        /**
+         * The default, lax policy which doesn't catch anything.
+         */
+        public static final VmPolicy LAX = new VmPolicy(0);
+
+        final int mask;
+
+        private VmPolicy(int mask) {
+            this.mask = mask;
+        }
+
+        @Override
+        public String toString() {
+            return "[StrictMode.VmPolicy; mask=" + mask + "]";
+        }
+
+        /**
+         * Creates {@link VmPolicy} instances.  Methods whose names start
+         * with {@code detect} specify what problems we should look
+         * for.  Methods whose names start with {@code penalty} specify what
+         * we should do when we detect a problem.
+         *
+         * <p>You can call as many {@code detect} and {@code penalty}
+         * methods as you like. Currently order is insignificant: all
+         * penalties apply to all detected problems.
+         *
+         * <p>For example, detect everything and log anything that's found:
+         * <pre>
+         * StrictMode.VmPolicy policy = new StrictMode.VmPolicy.Builder()
+         *     .detectAll()
+         *     .penaltyLog()
+         *     .build();
+         * StrictMode.setVmPolicy(policy);
+         * </pre>
+         */
+        public static final class Builder {
+            private int mMask;
+
+            /**
+             * Detect everything that's potentially suspect.
+             *
+             * <p>As of the Gingerbread release this only includes
+             * SQLite cursor leaks but will likely expand in future
+             * releases.
+             */
+            public Builder detectAll() {
+                return enable(DETECT_VM_CURSOR_LEAKS);
+            }
+
+            /**
+             * Detect when an
+             * {@link android.database.sqlite.SQLiteCursor} or other
+             * SQLite object is finalized without having been closed.
+             *
+             * <p>You always want to explicitly close your SQLite
+             * cursors to avoid unnecessary database contention and
+             * temporary memory leaks.
+             */
+            public Builder detectLeakedSqlLiteObjects() {
+                return enable(DETECT_VM_CURSOR_LEAKS);
+            }
+
+            /**
+             * Crashes the whole process on violation.  This penalty runs at
+             * the end of all enabled penalties so yo you'll still get
+             * your logging or other violations before the process dies.
+             */
+            public Builder penaltyDeath() {
+                return enable(PENALTY_DEATH);
+            }
+
+            /**
+             * Log detected violations to the system log.
+             */
+            public Builder penaltyLog() {
+                return enable(PENALTY_LOG);
+            }
+
+            /**
+             * Enable detected violations log a stacktrace and timing data
+             * to the {@link android.os.DropBoxManager DropBox} on policy
+             * violation.  Intended mostly for platform integrators doing
+             * beta user field data collection.
+             */
+            public Builder penaltyDropBox() {
+                return enable(PENALTY_DROPBOX);
+            }
+
+            private Builder enable(int bit) {
+                mMask |= bit;
+                return this;
+            }
+
+            /**
+             * Construct the VmPolicy instance.
+             *
+             * <p>Note: if no penalties are enabled before calling
+             * <code>build</code>, {@link #penaltyLog} is implicitly
+             * set.
+             */
+            public VmPolicy build() {
+                // If there are detection bits set but no violation bits
+                // set, enable simple logging.
+                if (mMask != 0 &&
+                    (mMask & (PENALTY_DEATH | PENALTY_LOG |
+                              PENALTY_DROPBOX | PENALTY_DIALOG)) == 0) {
+                    penaltyLog();
+                }
+                return new VmPolicy(mMask);
+            }
+        }
+    }
 
     /**
      * Log of strict mode violation stack traces that have occurred
@@ -181,19 +497,21 @@
     };
 
     /**
-     * Sets the policy for what actions the current thread isn't
-     * expected to do, as well as the penalty if it does.
+     * Sets the policy for what actions on the current thread should
+     * be detected, as well as the penalty if such actions occur.
      *
-     * <p>Internally this sets a thread-local integer which is
+     * <p>Internally this sets a thread-local variable which is
      * propagated across cross-process IPC calls, meaning you can
      * catch violations when a system service or another process
      * accesses the disk or network on your behalf.
      *
-     * @param policyMask a bitmask of DISALLOW_* and PENALTY_* values,
-     *     e.g. {@link #DISALLOW_DISK_READ}, {@link #DISALLOW_DISK_WRITE},
-     *     {@link #DISALLOW_NETWORK}, {@link #PENALTY_LOG}.
+     * @param policy the policy to put into place
      */
-    public static void setThreadPolicy(final int policyMask) {
+    public static void setThreadPolicy(final ThreadPolicy policy) {
+        setThreadPolicyMask(policy.mask);
+    }
+
+    private static void setThreadPolicyMask(final int policyMask) {
         // In addition to the Java-level thread-local in Dalvik's
         // BlockGuard, we also need to keep a native thread-local in
         // Binder in order to propagate the value across Binder calls,
@@ -222,65 +540,76 @@
 
     private static class StrictModeNetworkViolation extends BlockGuard.BlockGuardPolicyException {
         public StrictModeNetworkViolation(int policyMask) {
-            super(policyMask, DISALLOW_NETWORK);
+            super(policyMask, DETECT_NETWORK);
         }
     }
 
     private static class StrictModeDiskReadViolation extends BlockGuard.BlockGuardPolicyException {
         public StrictModeDiskReadViolation(int policyMask) {
-            super(policyMask, DISALLOW_DISK_READ);
+            super(policyMask, DETECT_DISK_READ);
         }
     }
 
     private static class StrictModeDiskWriteViolation extends BlockGuard.BlockGuardPolicyException {
         public StrictModeDiskWriteViolation(int policyMask) {
-            super(policyMask, DISALLOW_DISK_WRITE);
+            super(policyMask, DETECT_DISK_WRITE);
         }
     }
 
     /**
      * Returns the bitmask of the current thread's policy.
      *
-     * @return the bitmask of all the DISALLOW_* and PENALTY_* bits currently enabled
+     * @return the bitmask of all the DETECT_* and PENALTY_* bits currently enabled
+     *
+     * @hide
      */
-    public static int getThreadPolicy() {
+    public static int getThreadPolicyMask() {
         return BlockGuard.getThreadPolicy().getPolicyMask();
     }
 
     /**
-     * A convenience wrapper around {@link #getThreadPolicy} and
-     * {@link #setThreadPolicy}.  Updates the current thread's policy
-     * mask to allow both reading &amp; writing to disk, returning the
-     * old policy so you can restore it at the end of a block.
-     *
-     * @return the old policy mask, to be passed to setThreadPolicy to
-     *         restore the policy.
+     * Returns the current thread's policy.
      */
-    public static int allowThreadDiskWrites() {
-        int oldPolicy = getThreadPolicy();
-        int newPolicy = oldPolicy & ~(DISALLOW_DISK_WRITE | DISALLOW_DISK_READ);
-        if (newPolicy != oldPolicy) {
-            setThreadPolicy(newPolicy);
-        }
-        return oldPolicy;
+    public static ThreadPolicy getThreadPolicy() {
+        return new ThreadPolicy(getThreadPolicyMask());
     }
 
     /**
-     * A convenience wrapper around {@link #getThreadPolicy} and
-     * {@link #setThreadPolicy}.  Updates the current thread's policy
-     * mask to allow reading from disk, returning the old
-     * policy so you can restore it at the end of a block.
+     * A convenience wrapper that takes the current
+     * {@link ThreadPolicy} from {@link #getThreadPolicy}, modifies it
+     * to permit both disk reads &amp; writes, and sets the new policy
+     * with {@link #setThreadPolicy}, returning the old policy so you
+     * can restore it at the end of a block.
      *
-     * @return the old policy mask, to be passed to setThreadPolicy to
+     * @return the old policy, to be passed to {@link #setThreadPolicy} to
+     *         restore the policy at the end of a block
+     */
+    public static ThreadPolicy allowThreadDiskWrites() {
+        int oldPolicyMask = getThreadPolicyMask();
+        int newPolicyMask = oldPolicyMask & ~(DETECT_DISK_WRITE | DETECT_DISK_READ);
+        if (newPolicyMask != oldPolicyMask) {
+            setThreadPolicyMask(newPolicyMask);
+        }
+        return new ThreadPolicy(oldPolicyMask);
+    }
+
+    /**
+     * A convenience wrapper that takes the current
+     * {@link ThreadPolicy} from {@link #getThreadPolicy}, modifies it
+     * to permit disk reads, and sets the new policy
+     * with {@link #setThreadPolicy}, returning the old policy so you
+     * can restore it at the end of a block.
+     *
+     * @return the old policy, to be passed to setThreadPolicy to
      *         restore the policy.
      */
-    public static int allowThreadDiskReads() {
-        int oldPolicy = getThreadPolicy();
-        int newPolicy = oldPolicy & ~(DISALLOW_DISK_READ);
-        if (newPolicy != oldPolicy) {
-            setThreadPolicy(newPolicy);
+    public static ThreadPolicy allowThreadDiskReads() {
+        int oldPolicyMask = getThreadPolicyMask();
+        int newPolicyMask = oldPolicyMask & ~(DETECT_DISK_READ);
+        if (newPolicyMask != oldPolicyMask) {
+            setThreadPolicyMask(newPolicyMask);
         }
-        return oldPolicy;
+        return new ThreadPolicy(oldPolicyMask);
     }
 
     /**
@@ -294,11 +623,14 @@
         if ("user".equals(Build.TYPE)) {
             return false;
         }
-        StrictMode.setThreadPolicy(
-            StrictMode.DISALLOW_DISK_WRITE |
-            StrictMode.DISALLOW_DISK_READ |
-            StrictMode.DISALLOW_NETWORK |
+        StrictMode.setThreadPolicyMask(
+            StrictMode.DETECT_DISK_WRITE |
+            StrictMode.DETECT_DISK_READ |
+            StrictMode.DETECT_NETWORK |
             StrictMode.PENALTY_DROPBOX);
+        sVmPolicyMask = StrictMode.DETECT_VM_CURSOR_LEAKS |
+                StrictMode.PENALTY_DROPBOX |
+                StrictMode.PENALTY_LOG;
         return true;
     }
 
@@ -372,7 +704,7 @@
 
         // Part of BlockGuard.Policy interface:
         public void onWriteToDisk() {
-            if ((mPolicyMask & DISALLOW_DISK_WRITE) == 0) {
+            if ((mPolicyMask & DETECT_DISK_WRITE) == 0) {
                 return;
             }
             BlockGuard.BlockGuardPolicyException e = new StrictModeDiskWriteViolation(mPolicyMask);
@@ -382,7 +714,7 @@
 
         // Part of BlockGuard.Policy interface:
         public void onReadFromDisk() {
-            if ((mPolicyMask & DISALLOW_DISK_READ) == 0) {
+            if ((mPolicyMask & DETECT_DISK_READ) == 0) {
                 return;
             }
             BlockGuard.BlockGuardPolicyException e = new StrictModeDiskReadViolation(mPolicyMask);
@@ -392,7 +724,7 @@
 
         // Part of BlockGuard.Policy interface:
         public void onNetwork() {
-            if ((mPolicyMask & DISALLOW_NETWORK) == 0) {
+            if ((mPolicyMask & DETECT_NETWORK) == 0) {
                 return;
             }
             BlockGuard.BlockGuardPolicyException e = new StrictModeNetworkViolation(mPolicyMask);
@@ -547,13 +879,13 @@
             if (violationMaskSubset != 0) {
                 int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage);
                 violationMaskSubset |= violationBit;
-                final int savedPolicy = getThreadPolicy();
+                final int savedPolicyMask = getThreadPolicyMask();
                 try {
                     // First, remove any policy before we call into the Activity Manager,
                     // otherwise we'll infinite recurse as we try to log policy violations
                     // to disk, thus violating policy, thus requiring logging, etc...
                     // We restore the current policy below, in the finally block.
-                    setThreadPolicy(0);
+                    setThreadPolicyMask(0);
 
                     ActivityManagerNative.getDefault().handleApplicationStrictModeViolation(
                         RuntimeInit.getApplicationObject(),
@@ -563,7 +895,7 @@
                     Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
                 } finally {
                     // Restore the policy.
-                    setThreadPolicy(savedPolicy);
+                    setThreadPolicyMask(savedPolicyMask);
                 }
             }
 
@@ -592,6 +924,74 @@
     }
 
     /**
+     * Sets the policy for what actions in the VM process (on any
+     * thread) should be detected, as well as the penalty if such
+     * actions occur.
+     *
+     * @param policy the policy to put into place
+     */
+    public static void setVmPolicy(final VmPolicy policy) {
+        sVmPolicyMask = policy.mask;
+    }
+
+    /**
+     * Gets the current VM policy.
+     */
+    public static VmPolicy getVmPolicy() {
+        return new VmPolicy(sVmPolicyMask);
+    }
+
+    /**
+     * @hide
+     */
+    public static boolean vmSqliteObjectLeaksEnabled() {
+        return (sVmPolicyMask & DETECT_VM_CURSOR_LEAKS) != 0;
+    }
+
+    /**
+     * @hide
+     */
+    public static void onSqliteObjectLeaked(String message, Throwable originStack) {
+        if ((sVmPolicyMask & PENALTY_LOG) != 0) {
+            Log.e(TAG, message, originStack);
+        }
+
+        if ((sVmPolicyMask & PENALTY_DROPBOX) != 0) {
+            final ViolationInfo info = new ViolationInfo(originStack, sVmPolicyMask);
+
+            // The violationMask, passed to ActivityManager, is a
+            // subset of the original StrictMode policy bitmask, with
+            // only the bit violated and penalty bits to be executed
+            // by the ActivityManagerService remaining set.
+            int violationMaskSubset = PENALTY_DROPBOX | DETECT_VM_CURSOR_LEAKS;
+            final int savedPolicyMask = getThreadPolicyMask();
+            try {
+                // First, remove any policy before we call into the Activity Manager,
+                // otherwise we'll infinite recurse as we try to log policy violations
+                // to disk, thus violating policy, thus requiring logging, etc...
+                // We restore the current policy below, in the finally block.
+                setThreadPolicyMask(0);
+
+                ActivityManagerNative.getDefault().handleApplicationStrictModeViolation(
+                    RuntimeInit.getApplicationObject(),
+                    violationMaskSubset,
+                    info);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
+            } finally {
+                // Restore the policy.
+                setThreadPolicyMask(savedPolicyMask);
+            }
+        }
+
+        if ((sVmPolicyMask & PENALTY_DEATH) != 0) {
+            System.err.println("StrictMode VmPolicy violation with POLICY_DEATH; shutting down.");
+            Process.killProcess(Process.myPid());
+            System.exit(10);
+        }
+    }
+
+    /**
      * Called from Parcel.writeNoException()
      */
     /* package */ static void writeGatheredViolationsToParcel(Parcel p) {
@@ -621,7 +1021,7 @@
         new LogStackTrace().printStackTrace(new PrintWriter(sw));
         String ourStack = sw.toString();
 
-        int policyMask = getThreadPolicy();
+        int policyMask = getThreadPolicyMask();
         boolean currentlyGathering = (policyMask & PENALTY_GATHER) != 0;
 
         int numViolations = p.readInt();
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 4268618..8554ece 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -424,7 +424,6 @@
      * @param key secret used to encrypt the OBB; may be <code>null</code> if no
      *            encryption was used on the OBB.
      * @return whether the mount call was successfully queued or not
-     * @throws IllegalArgumentException when the OBB is already mounted
      */
     public boolean mountObb(String filename, String key, OnObbStateChangeListener listener) {
         try {
@@ -458,10 +457,8 @@
      * @param force whether to kill any programs using this in order to unmount
      *            it
      * @return whether the unmount call was successfully queued or not
-     * @throws IllegalArgumentException when OBB is not already mounted
      */
-    public boolean unmountObb(String filename, boolean force, OnObbStateChangeListener listener)
-            throws IllegalArgumentException {
+    public boolean unmountObb(String filename, boolean force, OnObbStateChangeListener listener) {
         try {
             mObbActionListener.addListener(listener);
             mMountService.unmountObb(filename, force, mObbActionListener);
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index 1e358c9..8fd0e0a 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -378,16 +378,6 @@
     }
 
     /**
-     * Returns whether the download is suspended. (i.e. whether the download
-     * won't complete without some action from outside the download
-     * manager).
-     * @hide
-     */
-    public static boolean isStatusSuspended(int status) {
-        return (status == STATUS_PENDING_PAUSED || status == STATUS_RUNNING_PAUSED);
-    }
-
-    /**
      * Returns whether the status is a success (i.e. 2xx).
      * @hide
      */
@@ -435,24 +425,12 @@
     public static final int STATUS_PENDING = 190;
 
     /**
-     * This download hasn't stated yet and is paused
-     * @hide
-     */
-    public static final int STATUS_PENDING_PAUSED = 191;
-
-    /**
      * This download has started
      * @hide
      */
     public static final int STATUS_RUNNING = 192;
 
     /**
-     * This download has started and is paused
-     * @hide
-     */
-    public static final int STATUS_RUNNING_PAUSED = 193;
-
-    /**
      * This download has successfully completed.
      * Warning: there might be other status values that indicate success
      * in the future.
@@ -980,15 +958,6 @@
         }
 
         /**
-         * Returns whether the download is suspended. (i.e. whether the download
-         * won't complete without some action from outside the download
-         * manager).
-         */
-        public static boolean isStatusSuspended(int status) {
-            return (status == STATUS_PENDING_PAUSED || status == STATUS_RUNNING_PAUSED);
-        }
-
-        /**
          * Returns whether the status is a success (i.e. 2xx).
          */
         public static boolean isStatusSuccess(int status) {
@@ -1030,19 +999,30 @@
         public static final int STATUS_PENDING = 190;
 
         /**
-         * This download hasn't stated yet and is paused
-         */
-        public static final int STATUS_PENDING_PAUSED = 191;
-
-        /**
          * This download has started
          */
         public static final int STATUS_RUNNING = 192;
 
         /**
-         * This download has started and is paused
+         * This download has been paused by the owning app.
          */
-        public static final int STATUS_RUNNING_PAUSED = 193;
+        public static final int STATUS_PAUSED_BY_APP = 193;
+
+        /**
+         * This download encountered some network error and is waiting before retrying the request.
+         */
+        public static final int STATUS_WAITING_TO_RETRY = 194;
+
+        /**
+         * This download is waiting for network connectivity to proceed.
+         */
+        public static final int STATUS_WAITING_FOR_NETWORK = 195;
+
+        /**
+         * This download exceeded a size limit for mobile networks and is waiting for a Wi-Fi
+         * connection to proceed.
+         */
+        public static final int STATUS_QUEUED_FOR_WIFI = 196;
 
         /**
          * This download has successfully completed.
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index 05cbeff..4edc01f 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -39,7 +39,7 @@
 /**
  * TODO: Move this to
  * java/services/com/android/server/BluetoothEventLoop.java
- * and make the contructor package private again.
+ * and make the constructor package private again.
  *
  * @hide
  */
@@ -252,10 +252,10 @@
         }
         String name = propValues[0];
         if (name.equals("Name")) {
+            mBluetoothService.setProperty(name, propValues[1]);
             Intent intent = new Intent(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
             intent.putExtra(BluetoothAdapter.EXTRA_LOCAL_NAME, propValues[1]);
             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
-            mBluetoothService.setProperty(name, propValues[1]);
         } else if (name.equals("Pairable") || name.equals("Discoverable")) {
             String pairable = name.equals("Pairable") ? propValues[1] :
                 mBluetoothService.getPropertyInternal("Pairable");
@@ -266,6 +266,7 @@
             if (pairable == null || discoverable == null)
                 return;
 
+            mBluetoothService.setProperty(name, propValues[1]);
             int mode = BluetoothService.bluezStringToScanMode(
                     pairable.equals("true"),
                     discoverable.equals("true"));
@@ -275,9 +276,9 @@
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
             }
-            mBluetoothService.setProperty(name, propValues[1]);
         } else if (name.equals("Discovering")) {
             Intent intent;
+            mBluetoothService.setProperty(name, propValues[1]);
             if (propValues[1].equals("true")) {
                 mBluetoothService.setIsDiscovering(true);
                 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
@@ -288,7 +289,6 @@
                 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
             }
             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
-            mBluetoothService.setProperty(name, propValues[1]);
         } else if (name.equals("Devices")) {
             String value = null;
             int len = Integer.valueOf(propValues[1]);
@@ -322,19 +322,20 @@
         }
         BluetoothDevice device = mAdapter.getRemoteDevice(address);
         if (name.equals("Name")) {
+            mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
             Intent intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED);
             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
             intent.putExtra(BluetoothDevice.EXTRA_NAME, propValues[1]);
             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
-            mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
         } else if (name.equals("Class")) {
+            mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
             Intent intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED);
             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
             intent.putExtra(BluetoothDevice.EXTRA_CLASS,
                     new BluetoothClass(Integer.valueOf(propValues[1])));
             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
-            mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
         } else if (name.equals("Connected")) {
+            mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
             Intent intent = null;
             if (propValues[1].equals("true")) {
                 intent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
@@ -348,7 +349,6 @@
             }
             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
-            mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
         } else if (name.equals("UUIDs")) {
             String uuid = null;
             int len = Integer.valueOf(propValues[1]);
@@ -754,7 +754,7 @@
 
     private void onRestartRequired() {
         if (mBluetoothService.isEnabled()) {
-            Log.e(TAG, "*** A serious error occured (did bluetoothd crash?) - " +
+            Log.e(TAG, "*** A serious error occurred (did bluetoothd crash?) - " +
                        "restarting Bluetooth ***");
             mHandler.sendEmptyMessage(EVENT_RESTART_BLUETOOTH);
         }
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index bd105a7..f8caa2c 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -500,7 +500,7 @@
                 // forked multiple times in a row, probably because there is
                 // some race in sdptool or bluez when operated in parallel.
                 // As a workaround, delay 500ms between each fork of sdptool.
-                // TODO: Don't fork sdptool in order to regsiter service
+                // TODO: Don't fork sdptool in order to register service
                 // records, use a DBUS call instead.
                 switch (msg.arg1) {
                 case 1:
@@ -713,7 +713,7 @@
     /** local cache of bonding state.
     /* we keep our own state to track the intermediate state BONDING, which
     /* bluez does not track.
-     * All addreses must be passed in upper case.
+     * All addresses must be passed in upper case.
      */
     public class BondState {
         private final HashMap<String, Integer> mState = new HashMap<String, Integer>();
@@ -976,7 +976,7 @@
             }
         }
 
-        // This function adds a bluetooth address to the auto pairing blacklis
+        // This function adds a bluetooth address to the auto pairing blacklist
         // file. These addresses are added to DynamicAddressBlacklistSection
         private void updateAutoPairingData(String address) {
             BufferedWriter out = null;
@@ -1106,7 +1106,7 @@
      * a device discoverable; you need to call setMode() to make the device
      * explicitly discoverable.
      *
-     * @param timeout_s The discoverable timeout in seconds.
+     * @param timeout The discoverable timeout in seconds.
      */
     public synchronized boolean setDiscoverableTimeout(int timeout) {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
@@ -2565,12 +2565,12 @@
     /*package*/ String getAddressFromObjectPath(String objectPath) {
         String adapterObjectPath = getPropertyInternal("ObjectPath");
         if (adapterObjectPath == null || objectPath == null) {
-            Log.e(TAG, "getAddressFromObjectPath: AdpaterObjectPath:" + adapterObjectPath +
+            Log.e(TAG, "getAddressFromObjectPath: AdapterObjectPath:" + adapterObjectPath +
                     "  or deviceObjectPath:" + objectPath + " is null");
             return null;
         }
         if (!objectPath.startsWith(adapterObjectPath)) {
-            Log.e(TAG, "getAddressFromObjectPath: AdpaterObjectPath:" + adapterObjectPath +
+            Log.e(TAG, "getAddressFromObjectPath: AdapterObjectPath:" + adapterObjectPath +
                     "  is not a prefix of deviceObjectPath:" + objectPath +
                     "bluetoothd crashed ?");
             return null;
diff --git a/core/java/android/util/CalendarUtils.java b/core/java/android/util/CalendarUtils.java
index 3d340d9..1b2a894 100644
--- a/core/java/android/util/CalendarUtils.java
+++ b/core/java/android/util/CalendarUtils.java
@@ -146,6 +146,9 @@
          * This formats a date/time range using Calendar's time zone and the
          * local conventions for the region of the device.
          *
+         * If the {@link DateUtils#FORMAT_UTC} flag is used it will pass in
+         * the UTC time zone instead.
+         *
          * @param context the context is required only if the time is shown
          * @param startMillis the start time in UTC milliseconds
          * @param endMillis the end time in UTC milliseconds
@@ -156,10 +159,16 @@
         public String formatDateRange(Context context, long startMillis,
                 long endMillis, int flags) {
             String date;
+            String tz;
+            if ((flags & DateUtils.FORMAT_UTC) != 0) {
+                tz = Time.TIMEZONE_UTC;
+            } else {
+                tz = getTimeZone(context, null);
+            }
             synchronized (mSB) {
                 mSB.setLength(0);
                 date = DateUtils.formatDateRange(context, mF, startMillis, endMillis, flags,
-                        getTimeZone(context, null)).toString();
+                        tz).toString();
             }
             return date;
         }
diff --git a/core/java/android/view/DragEvent.aidl b/core/java/android/view/DragEvent.aidl
new file mode 100644
index 0000000..f08943f
--- /dev/null
+++ b/core/java/android/view/DragEvent.aidl
@@ -0,0 +1,19 @@
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+package android.view;
+
+parcelable DragEvent;
diff --git a/core/java/android/view/DragEvent.java b/core/java/android/view/DragEvent.java
new file mode 100644
index 0000000..47c6d3c
--- /dev/null
+++ b/core/java/android/view/DragEvent.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** !!! TODO: real docs */
+public class DragEvent implements Parcelable {
+    private static final boolean TRACK_RECYCLED_LOCATION = false;
+
+    int mAction;
+    float mX, mY;
+    ClipDescription mClipDescription;
+    ClipData mClipData;
+
+    private DragEvent mNext;
+    private RuntimeException mRecycledLocation;
+    private boolean mRecycled;
+
+    private static final int MAX_RECYCLED = 10;
+    private static final Object gRecyclerLock = new Object();
+    private static int gRecyclerUsed = 0;
+    private static DragEvent gRecyclerTop = null;
+
+    /**
+     * action constants for DragEvent dispatch
+     */
+    public static final int ACTION_DRAG_STARTED = 1;
+    public static final int ACTION_DRAG_LOCATION = 2;
+    public static final int ACTION_DROP = 3;
+    public static final int ACTION_DRAG_ENDED = 4;
+    public static final int ACTION_DRAG_ENTERED = 5;
+    public static final int ACTION_DRAG_EXITED = 6;
+
+    /* hide the constructor behind package scope */
+    DragEvent() {
+    }
+
+    public static DragEvent obtain() {
+        return DragEvent.obtain(0, 0f, 0f, null, null);
+    }
+
+    public static DragEvent obtain(int action, float x, float y,
+            ClipDescription description, ClipData data) {
+        final DragEvent ev;
+        synchronized (gRecyclerLock) {
+            if (gRecyclerTop == null) {
+                return new DragEvent();
+            }
+            ev = gRecyclerTop;
+            gRecyclerTop = ev.mNext;
+            gRecyclerUsed -= 1;
+        }
+        ev.mRecycledLocation = null;
+        ev.mRecycled = false;
+        ev.mNext = null;
+
+        ev.mAction = action;
+        ev.mX = x;
+        ev.mY = y;
+        ev.mClipDescription = description;
+        ev.mClipData = data;
+
+        return ev;
+    }
+
+    public int getAction() {
+        return mAction;
+    }
+
+    public float getX() {
+        return mX;
+    }
+
+    public float getY() {
+        return mY;
+    }
+
+    public ClipData getClipData() {
+        return mClipData;
+    }
+
+    public ClipDescription getClipDescription() {
+        return mClipDescription;
+    }
+
+    /**
+     * Recycle the DragEvent, to be re-used by a later caller.  After calling
+     * this function you must never touch the event again.
+     */
+    public final void recycle() {
+        // Ensure recycle is only called once!
+        if (TRACK_RECYCLED_LOCATION) {
+            if (mRecycledLocation != null) {
+                throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation);
+            }
+            mRecycledLocation = new RuntimeException("Last recycled here");
+        } else {
+            if (mRecycled) {
+                throw new RuntimeException(toString() + " recycled twice!");
+            }
+            mRecycled = true;
+        }
+
+        mClipData = null;
+        mClipDescription = null;
+
+        synchronized (gRecyclerLock) {
+            if (gRecyclerUsed < MAX_RECYCLED) {
+                gRecyclerUsed++;
+                mNext = gRecyclerTop;
+                gRecyclerTop = this;
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "DragEvent{" + Integer.toHexString(System.identityHashCode(this))
+        + " action=" + mAction + " @ (" + mX + ", " + mY + ") desc=" + mClipDescription
+        + " data=" + mClipData
+        + "}";
+    }
+
+    /* Parcelable interface */
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mAction);
+        dest.writeFloat(mX);
+        dest.writeFloat(mY);
+        if (mClipData == null) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(1);
+            mClipData.writeToParcel(dest, flags);
+        }
+        if (mClipDescription == null) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(1);
+            mClipDescription.writeToParcel(dest, flags);
+        }
+    }
+
+    public static final Parcelable.Creator<DragEvent> CREATOR =
+        new Parcelable.Creator<DragEvent>() {
+        public DragEvent createFromParcel(Parcel in) {
+            DragEvent event = DragEvent.obtain();
+            event.mAction = in.readInt();
+            event.mX = in.readFloat();
+            event.mY = in.readFloat();
+            if (in.readInt() != 0) {
+                event.mClipData = ClipData.CREATOR.createFromParcel(in);
+            }
+            if (in.readInt() != 0) {
+                event.mClipDescription = ClipDescription.CREATOR.createFromParcel(in);
+            }
+            return event;
+        }
+
+        public DragEvent[] newArray(int size) {
+            return new DragEvent[size];
+        }
+    };
+}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 921018a..6b9cda0 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -17,10 +17,13 @@
 
 package android.view;
 
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
+import android.view.DragEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
@@ -64,4 +67,9 @@
     
     void dispatchWallpaperCommand(String action, int x, int y,
             int z, in Bundle extras, boolean sync);
+
+    /**
+     * Drag/drop events
+     */
+     void dispatchDragEvent(in DragEvent event);
 }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 7f10b76..79ea5b6 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -17,6 +17,7 @@
 
 package android.view;
 
+import android.content.ClipData;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -116,6 +117,30 @@
     boolean performHapticFeedback(IWindow window, int effectId, boolean always);
     
     /**
+     * Allocate the drag's thumbnail surface.  Also assigns a token that identifies
+     * the drag to the OS and passes that as the return value.  A return value of
+     * null indicates failure.
+     */
+    IBinder prepareDrag(IWindow window, boolean localOnly,
+            int thumbnailWidth, int thumbnailHeight, out Surface outSurface);
+
+    /**
+     * Initiate the drag operation itself
+     */
+    boolean performDrag(IWindow window, IBinder dragToken, float touchX, float touchY,
+            float thumbCenterX, float thumbCenterY, in ClipData data);
+
+    /**
+     * Tell the OS that we've just dragged into a View that is willing to accept the drop
+     */
+    void dragRecipientEntered(IWindow window);
+
+    /**
+     * Tell the OS that we've just dragged *off* of a View that was willing to accept the drop
+     */
+    void dragRecipientExited(IWindow window);
+
+    /**
      * For windows with the wallpaper behind them, and the wallpaper is
      * larger than the screen, set the offset within the screen.
      * For multi screen launcher type applications, xstep and ystep indicate
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 6705596..dfbe65c 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.graphics.Matrix;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
@@ -347,6 +348,8 @@
     private RuntimeException mRecycledLocation;
     private boolean mRecycled;
 
+    private native void nativeTransform(Matrix matrix);
+
     private MotionEvent(int pointerCount, int sampleCount) {
         mPointerIdentifiers = new int[pointerCount];
         mDataSamples = new float[pointerCount * sampleCount * NUM_SAMPLE_DATA];
@@ -1413,6 +1416,19 @@
         mYOffset = y - dataSamples[lastDataSampleIndex + SAMPLE_Y];
     }
     
+    /**
+     * Applies a transformation matrix to all of the points in the event.
+     *
+     * @param matrix The transformation matrix to apply.
+     */
+    public final void transform(Matrix matrix) {
+        if (matrix == null) {
+            throw new IllegalArgumentException("matrix must not be null");
+        }
+
+        nativeTransform(matrix);
+    }
+
     private final void getPointerCoordsAtSampleIndex(int sampleIndex,
             PointerCoords outPointerCoords) {
         final float[] dataSamples = mDataSamples;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 3b10437..011c3df 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.content.ClipData;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -614,6 +615,7 @@
  */
 public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
     private static final boolean DBG = false;
+    static final boolean DEBUG_DRAG = true;
 
     /**
      * The logging tag used by this class with android.util.Log.
@@ -1681,7 +1683,7 @@
      * The transform matrix for the View. This transform is calculated internally
      * based on the rotation, scaleX, and scaleY properties. The identity matrix
      * is used by default. Do *not* use this variable directly; instead call
-     * getMatrix(), which will automatically recalculate the matrix if necessary
+     * getInverseMatrix(), which will automatically recalculate the matrix if necessary
      * to get the correct matrix based on the latest rotation and scale properties.
      */
     private Matrix mInverseMatrix;
@@ -1704,7 +1706,8 @@
      * A variable that tracks whether we need to recalculate the
      * transform matrix, based on whether the rotation or scaleX/Y properties
      * have changed since the matrix was last calculated. This variable
-     * is only valid after a call to getMatrix().
+     * is only valid after a call to updateMatrix() or to a function that
+     * calls it such as getMatrix(), hasIdentityMatrix() and getInverseMatrix().
      */
     private boolean mMatrixIsIdentity = true;
 
@@ -2030,6 +2033,14 @@
     private int mTouchSlop;
 
     /**
+     * Cache drag/drop state
+     *
+     */
+    boolean mCanAcceptDrop;
+    private int mThumbnailWidth;
+    private int mThumbnailHeight;
+
+    /**
      * Simple constructor to use when creating a view from code.
      *
      * @param context The Context the view is running in, through which it can
@@ -5108,7 +5119,7 @@
      * @return The current transform matrix for the view
      */
     public Matrix getMatrix() {
-        hasIdentityMatrix();
+        updateMatrix();
         return mMatrix;
     }
 
@@ -5123,11 +5134,20 @@
     }
 
     /**
-     * Recomputes the transform matrix if necessary.
+     * Returns true if the transform matrix is the identity matrix.
+     * Recomputes the matrix if necessary.
      * 
      * @return True if the transform matrix is the identity matrix, false otherwise.
      */
-    boolean hasIdentityMatrix() {
+    final boolean hasIdentityMatrix() {
+        updateMatrix();
+        return mMatrixIsIdentity;
+    }
+
+    /**
+     * Recomputes the transform matrix if necessary.
+     */
+    private final void updateMatrix() {
         if (mMatrixDirty) {
             // transform-related properties have changed since the last time someone
             // asked for the matrix; recalculate it with the current values
@@ -5166,7 +5186,6 @@
             mMatrixIsIdentity = mMatrix.isIdentity();
             mInverseMatrixDirty = true;
         }
-        return mMatrixIsIdentity;
     }
 
     /**
@@ -5176,7 +5195,8 @@
      *
      * @return The inverse of the current matrix of this view.
      */
-    Matrix getInverseMatrix() {
+    final Matrix getInverseMatrix() {
+        updateMatrix();
         if (mInverseMatrixDirty) {
             if (mInverseMatrix == null) {
                 mInverseMatrix = new Matrix();
@@ -5464,7 +5484,8 @@
      */
     public final void setTop(int top) {
         if (top != mTop) {
-            if (hasIdentityMatrix()) {
+            updateMatrix();
+            if (mMatrixIsIdentity) {
                 final ViewParent p = mParent;
                 if (p != null && mAttachInfo != null) {
                     final Rect r = mAttachInfo.mTmpInvalRect;
@@ -5513,7 +5534,8 @@
      */
     public final void setBottom(int bottom) {
         if (bottom != mBottom) {
-            if (hasIdentityMatrix()) {
+            updateMatrix();
+            if (mMatrixIsIdentity) {
                 final ViewParent p = mParent;
                 if (p != null && mAttachInfo != null) {
                     final Rect r = mAttachInfo.mTmpInvalRect;
@@ -5559,8 +5581,8 @@
      */
     public final void setLeft(int left) {
         if (left != mLeft) {
-            System.out.println("view " + this + " left = " + left);
-            if (hasIdentityMatrix()) {
+            updateMatrix();
+            if (mMatrixIsIdentity) {
                 final ViewParent p = mParent;
                 if (p != null && mAttachInfo != null) {
                     final Rect r = mAttachInfo.mTmpInvalRect;
@@ -5609,7 +5631,8 @@
      */
     public final void setRight(int right) {
         if (right != mRight) {
-            if (hasIdentityMatrix()) {
+            updateMatrix();
+            if (mMatrixIsIdentity) {
                 final ViewParent p = mParent;
                 if (p != null && mAttachInfo != null) {
                     final Rect r = mAttachInfo.mTmpInvalRect;
@@ -5748,26 +5771,34 @@
      * @param outRect The hit rectangle of the view.
      */
     public void getHitRect(Rect outRect) {
-        if (hasIdentityMatrix() || mAttachInfo == null) {
+        updateMatrix();
+        if (mMatrixIsIdentity || mAttachInfo == null) {
             outRect.set(mLeft, mTop, mRight, mBottom);
         } else {
-            Matrix m = getMatrix();
             final RectF tmpRect = mAttachInfo.mTmpTransformRect;
             tmpRect.set(-mPivotX, -mPivotY, getWidth() - mPivotX, getHeight() - mPivotY);
-            m.mapRect(tmpRect);
+            mMatrix.mapRect(tmpRect);
             outRect.set((int) tmpRect.left + mLeft, (int) tmpRect.top + mTop,
                     (int) tmpRect.right + mLeft, (int) tmpRect.bottom + mTop);
         }
     }
 
     /**
+     * Determines whether the given point, in local coordinates is inside the view.
+     */
+    /*package*/ final boolean pointInView(float localX, float localY) {
+        return localX >= 0 && localX < (mRight - mLeft)
+                && localY >= 0 && localY < (mBottom - mTop);
+    }
+
+    /**
      * Utility method to determine whether the given point, in local coordinates,
      * is inside the view, where the area of the view is expanded by the slop factor.
      * This method is called while processing touch-move events to determine if the event
      * is still within the view.
      */
     private boolean pointInView(float localX, float localY, float slop) {
-        return localX > -slop && localY > -slop && localX < ((mRight - mLeft) + slop) &&
+        return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
                 localY < ((mBottom - mTop) + slop);
     }
 
@@ -5832,7 +5863,8 @@
      */
     public void offsetTopAndBottom(int offset) {
         if (offset != 0) {
-            if (hasIdentityMatrix()) {
+            updateMatrix();
+            if (mMatrixIsIdentity) {
                 final ViewParent p = mParent;
                 if (p != null && mAttachInfo != null) {
                     final Rect r = mAttachInfo.mTmpInvalRect;
@@ -5872,7 +5904,8 @@
      */
     public void offsetLeftAndRight(int offset) {
         if (offset != 0) {
-            if (hasIdentityMatrix()) {
+            updateMatrix();
+            if (mMatrixIsIdentity) {
                 final ViewParent p = mParent;
                 if (p != null && mAttachInfo != null) {
                     final Rect r = mAttachInfo.mTmpInvalRect;
@@ -9754,6 +9787,140 @@
     }
 
     /**
+     * Drag and drop.  App calls startDrag(), then callbacks to onMeasureDragThumbnail()
+     * and onDrawDragThumbnail() happen, then the drag operation is handed over to the
+     * OS.
+     * !!! TODO: real docs
+     * @hide
+     */
+    public final boolean startDrag(ClipData data, float touchX, float touchY,
+            float thumbnailTouchX, float thumbnailTouchY, boolean myWindowOnly) {
+        if (DEBUG_DRAG) {
+            Log.d(VIEW_LOG_TAG, "startDrag: touch=(" + touchX + "," + touchY
+                    + ") thumb=(" + thumbnailTouchX + "," + thumbnailTouchY
+                    + ") data=" + data + " local=" + myWindowOnly);
+        }
+        boolean okay = false;
+
+        measureThumbnail();     // throws if the view fails to specify dimensions
+
+        Surface surface = new Surface();
+        try {
+            IBinder token = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
+                    myWindowOnly, mThumbnailWidth, mThumbnailHeight, surface);
+            if (DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token=" + token
+                    + " surface=" + surface);
+            if (token != null) {
+                Canvas canvas = surface.lockCanvas(null);
+                try {
+                    onDrawDragThumbnail(canvas);
+                } finally {
+                    surface.unlockCanvasAndPost(canvas);
+                }
+
+                okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, token,
+                        touchX, touchY, thumbnailTouchX, thumbnailTouchY, data);
+                if (DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay);
+            }
+        } catch (Exception e) {
+            Log.e(VIEW_LOG_TAG, "Unable to initiate drag", e);
+            surface.destroy();
+        }
+
+        return okay;
+    }
+
+    private void measureThumbnail() {
+        mPrivateFlags &= ~MEASURED_DIMENSION_SET;
+
+        onMeasureDragThumbnail();
+
+        // flag not set, setDragThumbnailDimension() was not invoked, we raise
+        // an exception to warn the developer
+        if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
+            throw new IllegalStateException("onMeasureDragThumbnail() did not set the"
+                    + " measured dimension by calling setDragThumbnailDimension()");
+        }
+
+        if (DEBUG_DRAG) {
+            Log.d(VIEW_LOG_TAG, "Drag thumb measured: w=" + mThumbnailWidth
+                    + " h=" + mThumbnailHeight);
+        }
+    }
+
+    /**
+     * The View must call this method from onMeasureDragThumbnail() in order to
+     * specify the dimensions of the drag thumbnail image.
+     *
+     * @param width The desired thumbnail width.
+     * @param height The desired thumbnail height.
+     */
+    protected final void setDragThumbnailDimension(int width, int height) {
+        mPrivateFlags |= MEASURED_DIMENSION_SET;
+        mThumbnailWidth = width;
+        mThumbnailHeight = height;
+    }
+
+    /**
+     * The default implementation specifies a drag thumbnail that matches the
+     * View's current size and appearance.
+     */
+    protected void onMeasureDragThumbnail() {
+        setDragThumbnailDimension(getWidth(), getHeight());
+    }
+
+    /**
+     * The default implementation just draws the current View appearance as the thumbnail
+     * @param canvas
+     */
+    protected void onDrawDragThumbnail(Canvas canvas) {
+        draw(canvas);
+    }
+
+    /**
+     * Drag-and-drop event dispatch.  The event.getAction() verb is one of the DragEvent
+     * constants DRAG_STARTED_EVENT, DRAG_EVENT, DROP_EVENT, and DRAG_ENDED_EVENT.
+     *
+     * For DRAG_STARTED_EVENT, event.getClipDescription() describes the content
+     * being dragged.  onDragEvent() should return 'true' if the view can handle
+     * a drop of that content.  A view that returns 'false' here will receive no
+     * further calls to onDragEvent() about the drag/drop operation.
+     *
+     * For DRAG_ENTERED, event.getClipDescription() describes the content being
+     * dragged.  This will be the same content description passed in the
+     * DRAG_STARTED_EVENT invocation.
+     *
+     * For DRAG_EXITED, event.getClipDescription() describes the content being
+     * dragged.  This will be the same content description passed in the
+     * DRAG_STARTED_EVENT invocation.  The view should return to its approriate
+     * drag-acceptance visual state.
+     *
+     * For DRAG_LOCATION_EVENT, event.getX() and event.getY() give the location in View
+     * coordinates of the current drag point.  The view must return 'true' if it
+     * can accept a drop of the current drag content, false otherwise.
+     *
+     * For DROP_EVENT, event.getX() and event.getY() give the location of the drop
+     * within the view; also, event.getClipData() returns the full data payload
+     * being dropped.  The view should return 'true' if it consumed the dropped
+     * content, 'false' if it did not.
+     *
+     * For DRAG_ENDED_EVENT, the 'event' argument may be null.  The view should return
+     * to its normal visual state.
+     */
+    protected boolean onDragEvent(DragEvent event) {
+        return false;
+    }
+
+    /**
+     * Views typically don't need to override dispatchDragEvent(); it just calls
+     * onDragEvent(what, event) and passes the result up appropriately.
+     *
+     */
+    public boolean dispatchDragEvent(DragEvent event) {
+        return onDragEvent(event);
+    }
+
+    /**
      * This needs to be a better API (NOT ON VIEW) before it is exposed.  If
      * it is ever exposed at all.
      * @hide
@@ -9821,30 +9988,6 @@
                 ViewConfiguration.getLongPressTimeout() - delayOffset);
     }
 
-    private static int[] stateSetUnion(final int[] stateSet1, final int[] stateSet2) {
-        final int stateSet1Length = stateSet1.length;
-        final int stateSet2Length = stateSet2.length;
-        final int[] newSet = new int[stateSet1Length + stateSet2Length];
-        int k = 0;
-        int i = 0;
-        int j = 0;
-        // This is a merge of the two input state sets and assumes that the
-        // input sets are sorted by the order imposed by ViewDrawableStates.
-        for (int viewState : R.styleable.ViewDrawableStates) {
-            if (i < stateSet1Length && stateSet1[i] == viewState) {
-                newSet[k++] = viewState;
-                i++;
-            } else if (j < stateSet2Length && stateSet2[j] == viewState) {
-                newSet[k++] = viewState;
-                j++;
-            }
-            if (k > 1) {
-                assert(newSet[k - 1] > newSet[k - 2]);
-            }
-        }
-        return newSet;
-    }
-
     /**
      * Inflate a view from an XML resource.  This convenience method wraps the {@link
      * LayoutInflater} class, which provides a full range of options for view inflation.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 570e288..8a3db38 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -19,6 +19,8 @@
 import android.animation.LayoutTransition;
 import com.android.internal.R;
 
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
@@ -26,6 +28,7 @@
 import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.Paint;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
@@ -33,6 +36,7 @@
 import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.Animation;
@@ -68,6 +72,7 @@
 public abstract class ViewGroup extends View implements ViewParent, ViewManager {
 
     private static final boolean DBG = false;
+    private static final String TAG = "ViewGroup";
 
     /**
      * Views which have been hidden or removed which need to be animated on
@@ -105,16 +110,27 @@
      */
     private Transformation mInvalidationTransformation;
 
-    // Target of Motion events
-    private View mMotionTarget;
+    // View currently under an ongoing drag
+    private View mCurrentDragView;
 
-    // Targets of MotionEvents in split mode
-    private SplitMotionTargets mSplitMotionTargets;
+    // Does this group have a child that can accept the current drag payload?
+    private boolean mChildAcceptsDrag;
+
+    // Used during drag dispatch
+    private final PointF mLocalPoint = new PointF();
 
     // Layout animation
     private LayoutAnimationController mLayoutAnimationController;
     private Animation.AnimationListener mAnimationListener;
 
+    // First touch target in the linked list of touch targets.
+    private TouchTarget mFirstTouchTarget;
+
+    // Temporary arrays for splitting pointers.
+    private int[] mTmpPointerIndexMap;
+    private int[] mTmpPointerIds;
+    private MotionEvent.PointerCoords[] mTmpPointerCoords;
+
     /**
      * Internal flags.
      *
@@ -813,6 +829,136 @@
 
     /**
      * {@inheritDoc}
+     *
+     * !!! TODO: write real docs
+     */
+    @Override
+    public boolean dispatchDragEvent(DragEvent event) {
+        boolean retval = false;
+        final float tx = event.mX;
+        final float ty = event.mY;
+
+        // !!! BUGCHECK: If we have a ViewGroup, we must necessarily have a ViewRoot,
+        // so we don't need to check getRootView() for null here?
+        ViewRoot root = (ViewRoot)(getRootView().getParent());
+
+        // Dispatch down the view hierarchy
+        switch (event.mAction) {
+        case DragEvent.ACTION_DRAG_STARTED: {
+            // clear state to recalculate which views we drag over
+            root.setDragFocus(event, null);
+
+            // Now dispatch down to our children, caching the responses
+            mChildAcceptsDrag = false;
+            final int count = mChildrenCount;
+            final View[] children = mChildren;
+            for (int i = 0; i < count; i++) {
+                final boolean handled = children[i].dispatchDragEvent(event);
+                children[i].mCanAcceptDrop = handled;
+                if (handled) {
+                    mChildAcceptsDrag = true;
+                }
+            }
+
+            // Return HANDLED if one of our children can accept the drag
+            if (mChildAcceptsDrag) {
+                retval = true;
+            }
+        } break;
+
+        case DragEvent.ACTION_DRAG_ENDED: {
+            // Notify all of our children that the drag is over
+            final int count = mChildrenCount;
+            final View[] children = mChildren;
+            for (int i = 0; i < count; i++) {
+                children[i].dispatchDragEvent(event);
+            }
+            // We consider drag-ended to have been handled if one of our children
+            // had offered to handle the drag.
+            if (mChildAcceptsDrag) {
+                retval = true;
+            }
+        } break;
+
+        case DragEvent.ACTION_DRAG_LOCATION: {
+            // Find the [possibly new] drag target
+            final View target = findFrontmostDroppableChildAt(event.mX, event.mY, mLocalPoint);
+
+            // If we've changed apparent drag target, tell the view root which view
+            // we're over now.  This will in turn send out DRAG_ENTERED / DRAG_EXITED
+            // notifications as appropriate.
+            if (mCurrentDragView != target) {
+                root.setDragFocus(event, target);
+                mCurrentDragView = target;
+            }
+            
+            // Dispatch the actual drag location notice, localized into its coordinates
+            if (target != null) {
+                event.mX = mLocalPoint.x;
+                event.mY = mLocalPoint.y;
+
+                retval = target.dispatchDragEvent(event);
+
+                event.mX = tx;
+                event.mY = ty;
+            }
+        } break;
+
+        case DragEvent.ACTION_DROP: {
+            if (View.DEBUG_DRAG) Slog.d(TAG, "Drop event: " + event);
+            View target = findFrontmostDroppableChildAt(event.mX, event.mY, mLocalPoint);
+            if (target != null) {
+                event.mX = mLocalPoint.x;
+                event.mY = mLocalPoint.y;
+                retval = target.dispatchDragEvent(event);
+                event.mX = tx;
+                event.mY = ty;
+            }
+        } break;
+        }
+
+        // If none of our children could handle the event, try here
+        if (!retval) {
+            retval = onDragEvent(event);
+        }
+        return retval;
+    }
+
+    // Find the frontmost child view that lies under the given point, and calculate
+    // the position within its own local coordinate system.
+    View findFrontmostDroppableChildAt(float x, float y, PointF outLocalPoint) {
+        final float scrolledX = x + mScrollX;
+        final float scrolledY = y + mScrollY;
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = count - 1; i >= 0; i--) {
+            final View child = children[i];
+            if (child.mCanAcceptDrop == false) {
+                continue;
+            }
+
+            float localX = scrolledX - child.mLeft;
+            float localY = scrolledY - child.mTop;
+            if (!child.hasIdentityMatrix() && mAttachInfo != null) {
+                // non-identity matrix: transform the point into the view's coordinates
+                final float[] localXY = mAttachInfo.mTmpTransformLocation;
+                localXY[0] = localX;
+                localXY[1] = localY;
+                child.getInverseMatrix().mapPoints(localXY);
+                localX = localXY[0];
+                localY = localXY[1];
+            }
+            if (localX >= 0 && localY >= 0 && localX < (child.mRight - child.mLeft) &&
+                    localY < (child.mBottom - child.mTop)) {
+                outLocalPoint.set(localX, localY);
+                return child;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
      */
     @Override
     public boolean dispatchKeyEventPreIme(KeyEvent event) {
@@ -872,150 +1018,254 @@
             return false;
         }
 
-        if ((mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) == FLAG_SPLIT_MOTION_EVENTS) {
-            if (mSplitMotionTargets == null) {
-                mSplitMotionTargets = new SplitMotionTargets();
-            }
-            return dispatchSplitTouchEvent(ev);
+        final int action = ev.getAction();
+        final int actionMasked = action & MotionEvent.ACTION_MASK;
+
+        // Handle an initial down.
+        if (actionMasked == MotionEvent.ACTION_DOWN) {
+            // Throw away all previous state when starting a new touch gesture.
+            // The framework may have dropped the up or cancel event for the previous gesture
+            // due to an app switch, ANR, or some other state change.
+            cancelAndClearTouchTargets(ev);
+            resetTouchState();
         }
 
-        final int action = ev.getAction();
-        final float xf = ev.getX();
-        final float yf = ev.getY();
-        final float scrolledXFloat = xf + mScrollX;
-        final float scrolledYFloat = yf + mScrollY;
-
-        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
-
-        if (action == MotionEvent.ACTION_DOWN) {
-            if (mMotionTarget != null) {
-                // this is weird, we got a pen down, but we thought it was
-                // already down!
-                // XXX: We should probably send an ACTION_UP to the current
-                // target.
-                mMotionTarget = null;
+        // Check for interception.
+        final boolean intercepted;
+        if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
+            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
+            if (!disallowIntercept) {
+                intercepted = onInterceptTouchEvent(ev);
+                ev.setAction(action); // restore action in case onInterceptTouchEvent() changed it
+            } else {
+                intercepted = false;
             }
-            // If we're disallowing intercept or if we're allowing and we didn't
-            // intercept
-            if (disallowIntercept || !onInterceptTouchEvent(ev)) {
-                // reset this event's action (just to protect ourselves)
-                ev.setAction(MotionEvent.ACTION_DOWN);
-                // We know we want to dispatch the event down, find a child
-                // who can handle it, start with the front-most child.
-                final View[] children = mChildren;
-                final int count = mChildrenCount;
+        } else {
+            intercepted = true;
+        }
 
-                for (int i = count - 1; i >= 0; i--) {
-                    final View child = children[i];
-                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
-                            || child.getAnimation() != null) {
-                        // Single dispatch always picks its target based on the initial down
-                        // event's position - index 0
-                        if (dispatchTouchEventIfInView(child, ev, 0)) {
-                            mMotionTarget = child;
-                            return true;
+        // Check for cancelation.
+        final boolean canceled = resetCancelNextUpFlag(this)
+                || actionMasked == MotionEvent.ACTION_CANCEL;
+
+        // Update list of touch targets for pointer down, if needed.
+        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
+        TouchTarget newTouchTarget = null;
+        boolean alreadyDispatchedToNewTouchTarget = false;
+        if (!canceled && !intercepted) {
+            if (actionMasked == MotionEvent.ACTION_DOWN
+                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)) {
+                final int actionIndex = ev.getActionIndex(); // always 0 for down
+                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
+                        : TouchTarget.ALL_POINTER_IDS;
+
+                // Clean up earlier touch targets for this pointer id in case they
+                // have become out of sync.
+                removePointersFromTouchTargets(idBitsToAssign);
+
+                final int childrenCount = mChildrenCount;
+                if (childrenCount != 0) {
+                    // Find a child that can receive the event.  Scan children from front to back.
+                    final View[] children = mChildren;
+                    final float x = ev.getX(actionIndex);
+                    final float y = ev.getY(actionIndex);
+
+                    for (int i = childrenCount - 1; i >= 0; i--) {
+                        final View child = children[i];
+                        if ((child.mViewFlags & VISIBILITY_MASK) != VISIBLE
+                                && child.getAnimation() == null) {
+                            // Skip invisible child unless it is animating.
+                            continue;
+                        }
+
+                        if (!isTransformedTouchPointInView(x, y, child)) {
+                            // New pointer is out of child's bounds.
+                            continue;
+                        }
+
+                        newTouchTarget = getTouchTarget(child);
+                        if (newTouchTarget != null) {
+                            // Child is already receiving touch within its bounds.
+                            // Give it the new pointer in addition to the ones it is handling.
+                            newTouchTarget.pointerIdBits |= idBitsToAssign;
+                            break;
+                        }
+
+                        resetCancelNextUpFlag(child);
+                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
+                            // Child wants to receive touch within its bounds.
+                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
+                            alreadyDispatchedToNewTouchTarget = true;
+                            break;
                         }
                     }
                 }
+
+                if (newTouchTarget == null && mFirstTouchTarget != null) {
+                    // Did not find a child to receive the event.
+                    // Assign the pointer to the least recently added target.
+                    newTouchTarget = mFirstTouchTarget;
+                    while (newTouchTarget.next != null) {
+                        newTouchTarget = newTouchTarget.next;
+                    }
+                    newTouchTarget.pointerIdBits |= idBitsToAssign;
+                }
             }
         }
 
-        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
-                (action == MotionEvent.ACTION_CANCEL);
-
-        if (isUpOrCancel) {
-            // Note, we've already copied the previous state to our local
-            // variable, so this takes effect on the next event
-            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
-        }
-
-        // The event wasn't an ACTION_DOWN, dispatch it to our target if
-        // we have one.
-        final View target = mMotionTarget;
-        if (target == null) {
-            // We don't have a target, this means we're handling the
-            // event as a regular view.
-            ev.setLocation(xf, yf);
-            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
-                ev.setAction(MotionEvent.ACTION_CANCEL);
-                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
-            }
-            return super.dispatchTouchEvent(ev);
-        }
-
-        // Calculate the offset point into the target's local coordinates
-        float xc = scrolledXFloat - (float) target.mLeft;
-        float yc = scrolledYFloat - (float) target.mTop;
-        if (!target.hasIdentityMatrix() && mAttachInfo != null) {
-            // non-identity matrix: transform the point into the view's coordinates
-            final float[] localXY = mAttachInfo.mTmpTransformLocation;
-            localXY[0] = xc;
-            localXY[1] = yc;
-            target.getInverseMatrix().mapPoints(localXY);
-            xc = localXY[0];
-            yc = localXY[1];
-        }
-
-        // if have a target, see if we're allowed to and want to intercept its
-        // events
-        if (!disallowIntercept && onInterceptTouchEvent(ev)) {
-            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
-            ev.setAction(MotionEvent.ACTION_CANCEL);
-            ev.setLocation(xc, yc);
-            if (!target.dispatchTouchEvent(ev)) {
-                // target didn't handle ACTION_CANCEL. not much we can do
-                // but they should have.
-            }
-            // clear the target
-            mMotionTarget = null;
-            // Don't dispatch this event to our own view, because we already
-            // saw it when intercepting; we just want to give the following
-            // event to the normal onTouchEvent().
-            return true;
-        }
-
-        if (isUpOrCancel) {
-            mMotionTarget = null;
-        }
-
-        // finally offset the event to the target's coordinate system and
-        // dispatch the event.
-        ev.setLocation(xc, yc);
-
-        if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
-            ev.setAction(MotionEvent.ACTION_CANCEL);
-            target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
-            mMotionTarget = null;
-        }
-
-        if (target.dispatchTouchEvent(ev)) {
-            return true;
+        // Dispatch to touch targets.
+        boolean handled = false;
+        if (mFirstTouchTarget == null) {
+            // No touch targets so treat this as an ordinary view.
+            handled = dispatchTransformedTouchEvent(ev, canceled, null,
+                    TouchTarget.ALL_POINTER_IDS);
         } else {
-            ev.setLocation(xf, yf);
+            // Dispatch to touch targets, excluding the new touch target if we already
+            // dispatched to it.  Cancel touch targets if necessary.
+            TouchTarget predecessor = null;
+            TouchTarget target = mFirstTouchTarget;
+            while (target != null) {
+                final TouchTarget next = target.next;
+                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
+                    handled = true;
+                } else {
+                    final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
+                    if (dispatchTransformedTouchEvent(ev, cancelChild,
+                            target.child, target.pointerIdBits)) {
+                        handled = true;
+                    }
+                    if (cancelChild) {
+                        if (predecessor == null) {
+                            mFirstTouchTarget = next;
+                        } else {
+                            predecessor.next = next;
+                        }
+                        target.recycle();
+                        target = next;
+                        continue;
+                    }
+                }
+                predecessor = target;
+                target = next;
+            }
+        }
+
+        // Update list of touch targets for pointer up or cancel, if needed.
+        if (canceled || actionMasked == MotionEvent.ACTION_UP) {
+            resetTouchState();
+        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
+            final int actionIndex = ev.getActionIndex();
+            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
+            removePointersFromTouchTargets(idBitsToRemove);
+        }
+
+        return handled;
+    }
+
+    /* Resets all touch state in preparation for a new cycle. */
+    private final void resetTouchState() {
+        clearTouchTargets();
+        resetCancelNextUpFlag(this);
+        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
+    }
+
+    /* Resets the cancel next up flag.
+     * Returns true if the flag was previously set. */
+    private final boolean resetCancelNextUpFlag(View view) {
+        if ((view.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
+            view.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
+            return true;
         }
         return false;
     }
 
-    /**
-     * This method detects whether the pointer location at <code>pointerIndex</code> within
-     * <code>ev</code> is inside the specified view. If so, the transformed event is dispatched to
-     * <code>child</code>.
-     *
-     * @param child View to hit test against
-     * @param ev MotionEvent to test
-     * @param pointerIndex Index of the pointer within <code>ev</code> to test
-     * @return <code>false</code> if the hit test failed, or the result of
-     *         <code>child.dispatchTouchEvent</code>
-     */
-    private boolean dispatchTouchEventIfInView(View child, MotionEvent ev, int pointerIndex) {
-        final float x = ev.getX(pointerIndex);
-        final float y = ev.getY(pointerIndex);
-        final float scrolledX = x + mScrollX;
-        final float scrolledY = y + mScrollY;
-        float localX = scrolledX - child.mLeft;
-        float localY = scrolledY - child.mTop;
-        if (!child.hasIdentityMatrix() && mAttachInfo != null) {
-            // non-identity matrix: transform the point into the view's coordinates
+    /* Clears all touch targets. */
+    private final void clearTouchTargets() {
+        TouchTarget target = mFirstTouchTarget;
+        if (target != null) {
+            do {
+                TouchTarget next = target.next;
+                target.recycle();
+                target = next;
+            } while (target != null);
+            mFirstTouchTarget = null;
+        }
+    }
+
+    /* Cancels and clears all touch targets. */
+    private final void cancelAndClearTouchTargets(MotionEvent event) {
+        if (mFirstTouchTarget != null) {
+            boolean syntheticEvent = false;
+            if (event == null) {
+                final long now = SystemClock.uptimeMillis();
+                event = MotionEvent.obtain(now, now,
+                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+                syntheticEvent = true;
+            }
+
+            for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
+                resetCancelNextUpFlag(target.child);
+                dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
+            }
+            clearTouchTargets();
+
+            if (syntheticEvent) {
+                event.recycle();
+            }
+        }
+    }
+
+    /* Gets the touch target for specified child view.
+     * Returns null if not found. */
+    private final TouchTarget getTouchTarget(View child) {
+        for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
+            if (target.child == child) {
+                return target;
+            }
+        }
+        return null;
+    }
+
+    /* Adds a touch target for specified child to the beginning of the list.
+     * Assumes the target child is not already present. */
+    private final TouchTarget addTouchTarget(View child, int pointerIdBits) {
+        TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
+        target.next = mFirstTouchTarget;
+        mFirstTouchTarget = target;
+        return target;
+    }
+
+    /* Removes the pointer ids from consideration. */
+    private final void removePointersFromTouchTargets(int pointerIdBits) {
+        TouchTarget predecessor = null;
+        TouchTarget target = mFirstTouchTarget;
+        while (target != null) {
+            final TouchTarget next = target.next;
+            if ((target.pointerIdBits & pointerIdBits) != 0) {
+                target.pointerIdBits &= ~pointerIdBits;
+                if (target.pointerIdBits == 0) {
+                    if (predecessor == null) {
+                        mFirstTouchTarget = next;
+                    } else {
+                        predecessor.next = next;
+                    }
+                    target.recycle();
+                    target = next;
+                    continue;
+                }
+            }
+            predecessor = target;
+            target = next;
+        }
+    }
+
+    /* Returns true if a child view contains the specified point when transformed
+     * into its coordinate space.
+     * Child must not be null. */
+    private final boolean isTransformedTouchPointInView(float x, float y, View child) {
+        float localX = x + mScrollX - child.mLeft;
+        float localY = y + mScrollY - child.mTop;
+        if (! child.hasIdentityMatrix() && mAttachInfo != null) {
             final float[] localXY = mAttachInfo.mTmpTransformLocation;
             localXY[0] = localX;
             localXY[1] = localY;
@@ -1023,224 +1273,215 @@
             localX = localXY[0];
             localY = localXY[1];
         }
-        if (localX >= 0 && localY >= 0 && localX < (child.mRight - child.mLeft) &&
-                localY < (child.mBottom - child.mTop)) {
-            // It would be safer to clone the event here but we don't for performance.
-            // There are many subtle interactions in touch event dispatch; change at your own risk.
-            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
-            ev.offsetLocation(localX - x, localY - y);
-            if (child.dispatchTouchEvent(ev)) {
-                return true;
-            } else {
-                ev.offsetLocation(x - localX, y - localY);
-                return false;
-            }
-        }
-        return false;
+        return child.pointInView(localX, localY);
     }
 
-    private boolean dispatchSplitTouchEvent(MotionEvent ev) {
-        final SplitMotionTargets targets = mSplitMotionTargets;
-        final int action = ev.getAction();
-        final int maskedAction = ev.getActionMasked();
-        float xf = ev.getX();
-        float yf = ev.getY();
-        float scrolledXFloat = xf + mScrollX;
-        float scrolledYFloat = yf + mScrollY;
+    /* Transforms a motion event into the coordinate space of a particular child view,
+     * filters out irrelevant pointer ids, and overrides its action if necessary.
+     * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. */
+    private final boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
+            View child, int desiredPointerIdBits) {
+        final boolean handled;
 
-        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
+        // Canceling motions is a special case.  We don't need to perform any transformations
+        // or filtering.  The important part is the action, not the contents.
+        final int oldAction = event.getAction();
+        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
+            event.setAction(MotionEvent.ACTION_CANCEL);
+            if (child == null) {
+                handled = super.dispatchTouchEvent(event);
+            } else {
+                handled = child.dispatchTouchEvent(event);
+            }
+            event.setAction(oldAction);
+            return handled;
+        }
 
-        if (maskedAction == MotionEvent.ACTION_DOWN ||
-                maskedAction == MotionEvent.ACTION_POINTER_DOWN) {
-            final int actionIndex = ev.getActionIndex();
-            final int actionId = ev.getPointerId(actionIndex);
+        // Calculate the number of pointers to deliver.
+        final int oldPointerCount = event.getPointerCount();
+        int newPointerCount = 0;
+        if (desiredPointerIdBits == TouchTarget.ALL_POINTER_IDS) {
+            newPointerCount = oldPointerCount;
+        } else {
+            for (int i = 0; i < oldPointerCount; i++) {
+                final int pointerId = event.getPointerId(i);
+                final int pointerIdBit = 1 << pointerId;
+                if ((pointerIdBit & desiredPointerIdBits) != 0) {
+                    newPointerCount += 1;
+                }
+            }
+        }
 
-            // Clear out any current target for this ID.
-            // XXX: We should probably send an ACTION_UP to the current
-            // target if present.
-            targets.removeById(actionId);
+        // If for some reason we ended up in an inconsistent state where it looks like we
+        // might produce a motion event with no pointers in it, then drop the event.
+        if (newPointerCount == 0) {
+            return false;
+        }
 
-            // If we're disallowing intercept or if we're allowing and we didn't
-            // intercept
-            if (disallowIntercept || !onInterceptTouchEvent(ev)) {
-                // reset this event's action (just to protect ourselves)
-                ev.setAction(action);
-                // We know we want to dispatch the event down, try to find a child
-                // who can handle it, start with the front-most child.
-                final long downTime = ev.getEventTime();
-                final View[] children = mChildren;
-                final int count = mChildrenCount;
-                for (int i = count - 1; i >= 0; i--) {
-                    final View child = children[i];
-                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
-                            || child.getAnimation() != null) {
-                        final MotionEvent childEvent =
-                                targets.filterMotionEventForChild(ev, child, downTime);
-                        if (childEvent != null) {
-                            try {
-                                final int childActionIndex = childEvent.findPointerIndex(actionId);
-                                if (dispatchTouchEventIfInView(child, childEvent,
-                                        childActionIndex)) {
-                                    targets.add(actionId, child, downTime);
+        // If the number of pointers is the same and we don't need to perform any fancy
+        // irreversible transformations, then we can reuse the motion event for this
+        // dispatch as long as we are careful to revert any changes we make.
+        final boolean reuse = newPointerCount == oldPointerCount
+                && (child == null || child.hasIdentityMatrix());
+        if (reuse) {
+            if (child == null) {
+                handled = super.dispatchTouchEvent(event);
+            } else {
+                final float offsetX = mScrollX - child.mLeft;
+                final float offsetY = mScrollY - child.mTop;
+                event.offsetLocation(offsetX, offsetY);
 
-                                    return true;
-                                }
-                            } finally {
-                                childEvent.recycle();
+                handled = child.dispatchTouchEvent(event);
+
+                event.offsetLocation(-offsetX, -offsetY);
+            }
+            return handled;
+        }
+
+        // Make a copy of the event.
+        // If the number of pointers is different, then we need to filter out irrelevant pointers
+        // as we make a copy of the motion event.
+        MotionEvent transformedEvent;
+        if (newPointerCount == oldPointerCount) {
+            transformedEvent = MotionEvent.obtain(event);
+        } else {
+            growTmpPointerArrays(newPointerCount);
+            final int[] newPointerIndexMap = mTmpPointerIndexMap;
+            final int[] newPointerIds = mTmpPointerIds;
+            final MotionEvent.PointerCoords[] newPointerCoords = mTmpPointerCoords;
+
+            int newPointerIndex = 0;
+            int oldPointerIndex = 0;
+            while (newPointerIndex < newPointerCount) {
+                final int pointerId = event.getPointerId(oldPointerIndex);
+                final int pointerIdBits = 1 << pointerId;
+                if ((pointerIdBits & desiredPointerIdBits) != 0) {
+                    newPointerIndexMap[newPointerIndex] = oldPointerIndex;
+                    newPointerIds[newPointerIndex] = pointerId;
+                    if (newPointerCoords[newPointerIndex] == null) {
+                        newPointerCoords[newPointerIndex] = new MotionEvent.PointerCoords();
+                    }
+
+                    newPointerIndex += 1;
+                }
+                oldPointerIndex += 1;
+            }
+
+            final int newAction;
+            if (cancel) {
+                newAction = MotionEvent.ACTION_CANCEL;
+            } else {
+                final int oldMaskedAction = oldAction & MotionEvent.ACTION_MASK;
+                if (oldMaskedAction == MotionEvent.ACTION_POINTER_DOWN
+                        || oldMaskedAction == MotionEvent.ACTION_POINTER_UP) {
+                    final int changedPointerId = event.getPointerId(
+                            (oldAction & MotionEvent.ACTION_POINTER_INDEX_MASK)
+                                    >> MotionEvent.ACTION_POINTER_INDEX_SHIFT);
+                    final int changedPointerIdBits = 1 << changedPointerId;
+                    if ((changedPointerIdBits & desiredPointerIdBits) != 0) {
+                        if (newPointerCount == 1) {
+                            // The first/last pointer went down/up.
+                            newAction = oldMaskedAction == MotionEvent.ACTION_POINTER_DOWN
+                                    ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_UP;
+                        } else {
+                            // A secondary pointer went down/up.
+                            int newChangedPointerIndex = 0;
+                            while (newPointerIds[newChangedPointerIndex] != changedPointerId) {
+                                newChangedPointerIndex += 1;
                             }
+                            newAction = oldMaskedAction | (newChangedPointerIndex
+                                    << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
                         }
+                    } else {
+                        // An unrelated pointer changed.
+                        newAction = MotionEvent.ACTION_MOVE;
+                    }
+                } else {
+                    // Simple up/down/cancel/move motion action.
+                    newAction = oldMaskedAction;
+                }
+            }
+
+            transformedEvent = null;
+            final int historySize = event.getHistorySize();
+            for (int historyIndex = 0; historyIndex <= historySize; historyIndex++) {
+                for (newPointerIndex = 0; newPointerIndex < newPointerCount; newPointerIndex++) {
+                    final MotionEvent.PointerCoords c = newPointerCoords[newPointerIndex];
+                    oldPointerIndex = newPointerIndexMap[newPointerIndex];
+                    if (historyIndex != historySize) {
+                        event.getHistoricalPointerCoords(oldPointerIndex, historyIndex, c);
+                    } else {
+                        event.getPointerCoords(oldPointerIndex, c);
                     }
                 }
 
-                // Didn't find a new target. Do we have a "primary" target to send to?
-                final SplitMotionTargets.TargetInfo primaryTargetInfo = targets.getPrimaryTarget();
-                if (primaryTargetInfo != null) {
-                    final View primaryTarget = primaryTargetInfo.view;
-                    final MotionEvent childEvent = targets.filterMotionEventForChild(ev,
-                            primaryTarget, primaryTargetInfo.downTime);
-                    if (childEvent != null) {
-                        try {
-                            // Calculate the offset point into the target's local coordinates
-                            float xc = scrolledXFloat - (float) primaryTarget.mLeft;
-                            float yc = scrolledYFloat - (float) primaryTarget.mTop;
-                            if (!primaryTarget.hasIdentityMatrix() && mAttachInfo != null) {
-                                // non-identity matrix: transform the point into the view's
-                                // coordinates
-                                final float[] localXY = mAttachInfo.mTmpTransformLocation;
-                                localXY[0] = xc;
-                                localXY[1] = yc;
-                                primaryTarget.getInverseMatrix().mapPoints(localXY);
-                                xc = localXY[0];
-                                yc = localXY[1];
-                            }
-                            childEvent.setLocation(xc, yc);
-                            if (primaryTarget.dispatchTouchEvent(childEvent)) {
-                                targets.add(actionId, primaryTarget, primaryTargetInfo.downTime);
-                                return true;
-                            }
-                        } finally {
-                            childEvent.recycle();
-                        }
-                    }
+                final long eventTime;
+                if (historyIndex != historySize) {
+                    eventTime = event.getHistoricalEventTime(historyIndex);
+                } else {
+                    eventTime = event.getEventTime();
+                }
+
+                if (transformedEvent == null) {
+                    transformedEvent = MotionEvent.obtain(
+                            event.getDownTime(), eventTime, newAction,
+                            newPointerCount, newPointerIds, newPointerCoords,
+                            event.getMetaState(), event.getXPrecision(), event.getYPrecision(),
+                            event.getDeviceId(), event.getEdgeFlags(), event.getSource(),
+                            event.getFlags());
+                } else {
+                    transformedEvent.addBatch(eventTime, newPointerCoords, 0);
                 }
             }
         }
 
-        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
-                (action == MotionEvent.ACTION_CANCEL);
-
-        if (isUpOrCancel) {
-            // Note, we've already copied the previous state to our local
-            // variable, so this takes effect on the next event
-            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
-        }
-
-        if (targets.isEmpty()) {
-            // We don't have any targets, this means we're handling the
-            // event as a regular view.
-            ev.setLocation(xf, yf);
-            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
-                ev.setAction(MotionEvent.ACTION_CANCEL);
-                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
-            }
-            return super.dispatchTouchEvent(ev);
-        }
-
-        // if we have targets, see if we're allowed to and want to intercept their
-        // events
-        int uniqueTargetCount = targets.getUniqueTargetCount();
-        if (!disallowIntercept && onInterceptTouchEvent(ev)) {
-            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
-
-            for (int uniqueIndex = 0; uniqueIndex < uniqueTargetCount; uniqueIndex++) {
-                final View target = targets.getUniqueTargetAt(uniqueIndex).view;
-
-                // Calculate the offset point into the target's local coordinates
-                float xc = scrolledXFloat - (float) target.mLeft;
-                float yc = scrolledYFloat - (float) target.mTop;
-                if (!target.hasIdentityMatrix() && mAttachInfo != null) {
-                    // non-identity matrix: transform the point into the view's coordinates
-                    final float[] localXY = mAttachInfo.mTmpTransformLocation;
-                    localXY[0] = xc;
-                    localXY[1] = yc;
-                    target.getInverseMatrix().mapPoints(localXY);
-                    xc = localXY[0];
-                    yc = localXY[1];
-                }
-
-                ev.setAction(MotionEvent.ACTION_CANCEL);
-                ev.setLocation(xc, yc);
-                if (!target.dispatchTouchEvent(ev)) {
-                    // target didn't handle ACTION_CANCEL. not much we can do
-                    // but they should have.
-                }
-            }
-            targets.clear();
-            // Don't dispatch this event to our own view, because we already
-            // saw it when intercepting; we just want to give the following
-            // event to the normal onTouchEvent().
-            return true;
-        }
-
-        boolean handled = false;
-        for (int uniqueIndex = 0; uniqueIndex < uniqueTargetCount; uniqueIndex++) {
-            final SplitMotionTargets.TargetInfo targetInfo = targets.getUniqueTargetAt(uniqueIndex);
-            final View target = targetInfo.view;
-
-            final MotionEvent targetEvent =
-                    targets.filterMotionEventForChild(ev, target, targetInfo.downTime);
-            if (targetEvent == null) {
-                continue;
+        // Perform any necessary transformations and dispatch.
+        if (child == null) {
+            handled = super.dispatchTouchEvent(transformedEvent);
+        } else {
+            final float offsetX = mScrollX - child.mLeft;
+            final float offsetY = mScrollY - child.mTop;
+            transformedEvent.offsetLocation(offsetX, offsetY);
+            if (! child.hasIdentityMatrix()) {
+                transformedEvent.transform(child.getInverseMatrix());
             }
 
-            try {
-                // Calculate the offset point into the target's local coordinates
-                xf = targetEvent.getX();
-                yf = targetEvent.getY();
-                scrolledXFloat = xf + mScrollX;
-                scrolledYFloat = yf + mScrollY;
-                float xc = scrolledXFloat - (float) target.mLeft;
-                float yc = scrolledYFloat - (float) target.mTop;
-                if (!target.hasIdentityMatrix() && mAttachInfo != null) {
-                    // non-identity matrix: transform the point into the view's coordinates
-                    final float[] localXY = mAttachInfo.mTmpTransformLocation;
-                    localXY[0] = xc;
-                    localXY[1] = yc;
-                    target.getInverseMatrix().mapPoints(localXY);
-                    xc = localXY[0];
-                    yc = localXY[1];
-                }
-
-                // finally offset the event to the target's coordinate system and
-                // dispatch the event.
-                targetEvent.setLocation(xc, yc);
-
-                if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
-                    targetEvent.setAction(MotionEvent.ACTION_CANCEL);
-                    target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
-                    targets.removeView(target);
-                    uniqueIndex--;
-                    uniqueTargetCount--;
-                }
-
-                handled |= target.dispatchTouchEvent(targetEvent);
-            } finally {
-                targetEvent.recycle();
-            }
+            handled = child.dispatchTouchEvent(transformedEvent);
         }
 
-        if (maskedAction == MotionEvent.ACTION_POINTER_UP) {
-            final int removeId = ev.getPointerId(ev.getActionIndex());
-            targets.removeById(removeId);
-        }
-
-        if (isUpOrCancel) {
-            targets.clear();
-        }
-
+        // Done.
+        transformedEvent.recycle();
         return handled;
     }
 
+    /* Enlarge the temporary pointer arrays for splitting pointers.
+     * May discard contents (but keeps PointerCoords objects to avoid reallocating them). */
+    private final void growTmpPointerArrays(int desiredCapacity) {
+        final MotionEvent.PointerCoords[] oldTmpPointerCoords = mTmpPointerCoords;
+        int capacity;
+        if (oldTmpPointerCoords != null) {
+            capacity = oldTmpPointerCoords.length;
+            if (desiredCapacity <= capacity) {
+                return;
+            }
+        } else {
+            capacity = 4;
+        }
+
+        while (capacity < desiredCapacity) {
+            capacity *= 2;
+        }
+
+        mTmpPointerIndexMap = new int[capacity];
+        mTmpPointerIds = new int[capacity];
+        mTmpPointerCoords = new MotionEvent.PointerCoords[capacity];
+
+        if (oldTmpPointerCoords != null) {
+            System.arraycopy(oldTmpPointerCoords, 0, mTmpPointerCoords, 0,
+                    oldTmpPointerCoords.length);
+        }
+    }
+
     /**
      * Enable or disable the splitting of MotionEvents to multiple children during touch event
      * dispatch. This behavior is disabled by default.
@@ -1262,7 +1503,6 @@
             mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
         } else {
             mGroupFlags &= ~FLAG_SPLIT_MOTION_EVENTS;
-            mSplitMotionTargets = null;
         }
     }
 
@@ -1473,19 +1713,12 @@
      */
     @Override
     void dispatchDetachedFromWindow() {
-        // If we still have a motion target, we are still in the process of
+        // If we still have a touch target, we are still in the process of
         // dispatching motion events to a child; we need to get rid of that
         // child to avoid dispatching events to it after the window is torn
         // down. To make sure we keep the child in a consistent state, we
         // first send it an ACTION_CANCEL motion event.
-        if (mMotionTarget != null) {
-            final long now = SystemClock.uptimeMillis();
-            final MotionEvent event = MotionEvent.obtain(now, now,
-                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
-            mMotionTarget.dispatchTouchEvent(event);
-            event.recycle();
-            mMotionTarget = null;
-        }
+        cancelAndClearTouchTargets(null);
 
         final int count = mChildrenCount;
         final View[] children = mChildren;
@@ -4287,290 +4520,57 @@
         }
     }
 
-    private static class SplitMotionTargets {
-        private SparseArray<View> mTargets;
-        private TargetInfo[] mUniqueTargets;
-        private int mUniqueTargetCount;
-        private MotionEvent.PointerCoords[] mPointerCoords;
-        private int[] mPointerIds;
+    /* Describes a touched view and the ids of the pointers that it has captured.
+     *
+     * This code assumes that pointer ids are always in the range 0..31 such that
+     * it can use a bitfield to track which pointer ids are present.
+     * As it happens, the lower layers of the input dispatch pipeline also use the
+     * same trick so the assumption should be safe here...
+     */
+    private static final class TouchTarget {
+        private static final int MAX_RECYCLED = 32;
+        private static final Object sRecycleLock = new Object();
+        private static TouchTarget sRecycleBin;
+        private static int sRecycledCount;
 
-        private static final int INITIAL_UNIQUE_MOTION_TARGETS_SIZE = 5;
-        private static final int INITIAL_BUCKET_SIZE = 5;
+        public static final int ALL_POINTER_IDS = -1; // all ones
 
-        public SplitMotionTargets() {
-            mTargets = new SparseArray<View>();
-            mUniqueTargets = new TargetInfo[INITIAL_UNIQUE_MOTION_TARGETS_SIZE];
-            mPointerIds = new int[INITIAL_BUCKET_SIZE];
-            mPointerCoords = new MotionEvent.PointerCoords[INITIAL_BUCKET_SIZE];
-            for (int i = 0; i < INITIAL_BUCKET_SIZE; i++) {
-                mPointerCoords[i] = new MotionEvent.PointerCoords();
-            }
+        // The touched child view.
+        public View child;
+
+        // The combined bit mask of pointer ids for all pointers captured by the target.
+        public int pointerIdBits;
+
+        // The next target in the target list.
+        public TouchTarget next;
+
+        private TouchTarget() {
         }
 
-        public void clear() {
-            mTargets.clear();
-            final int count = mUniqueTargetCount;
-            for (int i = 0; i < count; i++) {
-                mUniqueTargets[i].recycle();
-                mUniqueTargets[i] = null;
-            }
-            mUniqueTargetCount = 0;
-        }
-
-        public void add(int pointerId, View target, long downTime) {
-            mTargets.put(pointerId, target);
-
-            final int uniqueCount = mUniqueTargetCount;
-            boolean addUnique = true;
-            for (int i = 0; i < uniqueCount; i++) {
-                if (mUniqueTargets[i].view == target) {
-                    addUnique = false;
-                }
-            }
-            if (addUnique) {
-                if (mUniqueTargets.length == uniqueCount) {
-                    TargetInfo[] newTargets =
-                        new TargetInfo[uniqueCount + INITIAL_UNIQUE_MOTION_TARGETS_SIZE];
-                    System.arraycopy(mUniqueTargets, 0, newTargets, 0, uniqueCount);
-                    mUniqueTargets = newTargets;
-                }
-                mUniqueTargets[uniqueCount] = TargetInfo.obtain(target, downTime);
-                mUniqueTargetCount++;
-            }
-        }
-
-        public int getIdCount() {
-            return mTargets.size();
-        }
-
-        public int getUniqueTargetCount() {
-            return mUniqueTargetCount;
-        }
-
-        public TargetInfo getUniqueTargetAt(int index) {
-            return mUniqueTargets[index];
-        }
-
-        public View get(int id) {
-            return mTargets.get(id);
-        }
-
-        public int indexOfTarget(View target) {
-            return mTargets.indexOfValue(target);
-        }
-
-        public View targetAt(int index) {
-            return mTargets.valueAt(index);
-        }
-
-        public TargetInfo getPrimaryTarget() {
-            if (!isEmpty()) {
-                // Find the longest-lived target
-                long firstTime = Long.MAX_VALUE;
-                int firstIndex = 0;
-                final int uniqueCount = mUniqueTargetCount;
-                for (int i = 0; i < uniqueCount; i++) {
-                    TargetInfo info = mUniqueTargets[i];
-                    if (info.downTime < firstTime) {
-                        firstTime = info.downTime;
-                        firstIndex = i;
-                    }
-                }
-                return mUniqueTargets[firstIndex];
-            }
-            return null;
-        }
-
-        public boolean isEmpty() {
-            return mUniqueTargetCount == 0;
-        }
-
-        public void removeById(int id) {
-            final int index = mTargets.indexOfKey(id);
-            removeAt(index);
-        }
-        
-        public void removeView(View view) {
-            int i = 0;
-            while (i < mTargets.size()) {
-                if (mTargets.valueAt(i) == view) {
-                    mTargets.removeAt(i);
-                } else {
-                    i++;
-                }
-            }
-            removeUnique(view);
-        }
-
-        public void removeAt(int index) {
-            if (index < 0 || index >= mTargets.size()) {
-                return;
-            }
-
-            final View removeView = mTargets.valueAt(index);
-            mTargets.removeAt(index);
-            if (mTargets.indexOfValue(removeView) < 0) {
-                removeUnique(removeView);
-            }
-        }
-
-        private void removeUnique(View removeView) {
-            TargetInfo[] unique = mUniqueTargets;
-            int uniqueCount = mUniqueTargetCount;
-            for (int i = 0; i < uniqueCount; i++) {
-                if (unique[i].view == removeView) {
-                    unique[i].recycle();
-                    unique[i] = unique[--uniqueCount];
-                    unique[uniqueCount] = null;
-                    break;
-                }
-            }
-
-            mUniqueTargetCount = uniqueCount;
-        }
-
-        /**
-         * Return a new (obtain()ed) MotionEvent containing only data for pointers that should
-         * be dispatched to child. Don't forget to recycle it!
-         */
-        public MotionEvent filterMotionEventForChild(MotionEvent ev, View child, long downTime) {
-            int action = ev.getAction();
-            final int maskedAction = action & MotionEvent.ACTION_MASK;
-
-            // Only send pointer up events if this child was the target. Drop it otherwise.
-            if (maskedAction == MotionEvent.ACTION_POINTER_UP &&
-                    get(ev.getPointerId(ev.getActionIndex())) != child) {
-                return null;
-            }
-
-            int pointerCount = 0;
-            final int idCount = getIdCount();
-            for (int i = 0; i < idCount; i++) {
-                if (targetAt(i) == child) {
-                    pointerCount++;
-                }
-            }
-
-            int actionId = -1;
-            boolean needsNewIndex = false; // True if we should fill in the action's masked index
-
-            // If we have a down event, it wasn't counted above.
-            if (maskedAction == MotionEvent.ACTION_DOWN) {
-                pointerCount++;
-                actionId = ev.getPointerId(0);
-            } else if (maskedAction == MotionEvent.ACTION_POINTER_DOWN) {
-                pointerCount++;
-
-                actionId = ev.getPointerId(ev.getActionIndex());
-
-                if (indexOfTarget(child) < 0) {
-                    // The new action should be ACTION_DOWN if this child isn't currently getting
-                    // any events.
-                    action = MotionEvent.ACTION_DOWN;
-                } else {
-                    // Fill in the index portion of the action later.
-                    needsNewIndex = true;
-                }
-            } else if (maskedAction == MotionEvent.ACTION_POINTER_UP) {
-                actionId = ev.getPointerId(ev.getActionIndex());
-                if (pointerCount == 1) {
-                    // The new action should be ACTION_UP if there's only one pointer left for
-                    // this target.
-                    action = MotionEvent.ACTION_UP;
-                } else {
-                    // Fill in the index portion of the action later.
-                    needsNewIndex = true;
-                }
-            }
-
-            if (pointerCount == 0) {
-                return null;
-            }
-
-            // Fill the buckets with pointer data!
-            final int eventPointerCount = ev.getPointerCount();
-            int bucketIndex = 0;
-            int newActionIndex = -1;
-            for (int evp = 0; evp < eventPointerCount; evp++) {
-                final int id = ev.getPointerId(evp);
-
-                // Add this pointer to the bucket if it is new or targeted at child
-                if (id == actionId || get(id) == child) {
-                    // Expand scratch arrays if needed
-                    if (mPointerCoords.length <= bucketIndex) {
-                        int[] pointerIds = new int[pointerCount];
-                        MotionEvent.PointerCoords[] pointerCoords =
-                                new MotionEvent.PointerCoords[pointerCount];
-                        for (int i = mPointerCoords.length; i < pointerCoords.length; i++) {
-                            pointerCoords[i] = new MotionEvent.PointerCoords();
-                        }
-
-                        System.arraycopy(mPointerCoords, 0,
-                                pointerCoords, 0, mPointerCoords.length);
-                        System.arraycopy(mPointerIds, 0, pointerIds, 0, mPointerIds.length);
-
-                        mPointerCoords = pointerCoords;
-                        mPointerIds = pointerIds;
-                    }
-
-                    mPointerIds[bucketIndex] = id;
-                    ev.getPointerCoords(evp, mPointerCoords[bucketIndex]);
-
-                    if (needsNewIndex && id == actionId) {
-                        newActionIndex = bucketIndex;
-                    }
-
-                    bucketIndex++;
-                }
-            }
-
-            // Encode the new action index if we have one
-            if (newActionIndex >= 0) {
-                action = (action & MotionEvent.ACTION_MASK) |
-                        (newActionIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
-            }
-
-            return MotionEvent.obtain(downTime, ev.getEventTime(),
-                    action, pointerCount, mPointerIds, mPointerCoords, ev.getMetaState(),
-                    ev.getXPrecision(), ev.getYPrecision(), ev.getDeviceId(), ev.getEdgeFlags(),
-                    ev.getSource(), ev.getFlags());
-        }
-
-        static class TargetInfo {
-            public View view;
-            public long downTime;
-
-            private TargetInfo mNextRecycled;
-
-            private static TargetInfo sRecycleBin;
-            private static int sRecycledCount;
-
-            private static int MAX_RECYCLED = 15;
-
-            private TargetInfo() {
-            }
-
-            public static TargetInfo obtain(View v, long time) {
-                TargetInfo info;
+        public static TouchTarget obtain(View child, int pointerIdBits) {
+            final TouchTarget target;
+            synchronized (sRecycleLock) {
                 if (sRecycleBin == null) {
-                    info = new TargetInfo();
+                    target = new TouchTarget();
                 } else {
-                    info = sRecycleBin;
-                    sRecycleBin = info.mNextRecycled;
-                    sRecycledCount--;
+                    target = sRecycleBin;
+                    sRecycleBin = target.next;
+                     sRecycledCount--;
+                    target.next = null;
                 }
-                info.view = v;
-                info.downTime = time;
-                return info;
             }
+            target.child = child;
+            target.pointerIdBits = pointerIdBits;
+            return target;
+        }
 
-            public void recycle() {
-                if (sRecycledCount >= MAX_RECYCLED) {
-                    return;
+        public void recycle() {
+            synchronized (sRecycleLock) {
+                if (sRecycledCount < MAX_RECYCLED) {
+                    next = sRecycleBin;
+                    sRecycleBin = this;
+                    sRecycledCount += 1;
                 }
-                mNextRecycled = sRecycleBin;
-                sRecycleBin = this;
-                sRecycledCount++;
             }
         }
     }
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 77ba6fe..c63f7d6 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -24,6 +24,7 @@
 import android.graphics.Canvas;
 import android.graphics.PixelFormat;
 import android.graphics.PorterDuff;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.*;
@@ -45,6 +46,8 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.ComponentCallbacks;
 import android.content.Context;
 import android.app.ActivityManagerNative;
@@ -198,6 +201,11 @@
 
     final ViewConfiguration mViewConfiguration;
 
+    /* Drag/drop */
+    ClipDescription mDragDescription;
+    View mCurrentDragView;
+    final PointF mDragPoint = new PointF();
+
     /**
      * see {@link #playSoundEffect(int)}
      */
@@ -1670,6 +1678,7 @@
     public final static int FINISH_INPUT_CONNECTION = 1012;
     public final static int CHECK_FOCUS = 1013;
     public final static int CLOSE_SYSTEM_DIALOGS = 1014;
+    public final static int DISPATCH_DRAG_EVENT = 1015;
 
     @Override
     public void handleMessage(Message msg) {
@@ -1845,6 +1854,9 @@
                 mView.onCloseSystemDialogs((String)msg.obj);
             }
         } break;
+        case DISPATCH_DRAG_EVENT: {
+            handleDragEvent((DragEvent)msg.obj);
+        } break;
         }
     }
     
@@ -2434,6 +2446,87 @@
         }
     }
 
+    /* drag/drop */
+    private void handleDragEvent(DragEvent event) {
+        // From the root, only drag start/end/location are dispatched.  entered/exited
+        // are determined and dispatched by the viewgroup hierarchy, who then report
+        // that back here for ultimate reporting back to the framework.
+        if (mView != null && mAdded) {
+            final int what = event.mAction;
+
+            if (what == DragEvent.ACTION_DRAG_EXITED) {
+                // A direct EXITED event means that the window manager knows we've just crossed
+                // a window boundary, so the current drag target within this one must have
+                // just been exited.  Send it the usual notifications and then we're done
+                // for now.
+                setDragFocus(event, null);
+            } else {
+                // Cache the drag description when the operation starts, then fill it in
+                // on subsequent calls as a convenience
+                if (what == DragEvent.ACTION_DRAG_STARTED) {
+                    mDragDescription = event.mClipDescription;
+                } else {
+                    event.mClipDescription = mDragDescription;
+                }
+
+                // For events with a [screen] location, translate into window coordinates
+                if ((what == DragEvent.ACTION_DRAG_LOCATION) || (what == DragEvent.ACTION_DROP)) {
+                    mDragPoint.set(event.mX, event.mY);
+                    if (mTranslator != null) {
+                        mTranslator.translatePointInScreenToAppWindow(mDragPoint);
+                    }
+
+                    if (mCurScrollY != 0) {
+                        mDragPoint.offset(0, mCurScrollY);
+                    }
+
+                    event.mX = mDragPoint.x;
+                    event.mY = mDragPoint.y;
+                }
+
+                // Remember who the current drag target is pre-dispatch
+                final View prevDragView = mCurrentDragView;
+
+                // Now dispatch the drag/drop event
+                mView.dispatchDragEvent(event);
+
+                // If we changed apparent drag target, tell the OS about it
+                if (prevDragView != mCurrentDragView) {
+                    try {
+                        if (prevDragView != null) {
+                            sWindowSession.dragRecipientExited(mWindow);
+                        }
+                        if (mCurrentDragView != null) {
+                            sWindowSession.dragRecipientEntered(mWindow);
+                        }
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Unable to note drag target change");
+                    }
+                    mCurrentDragView = prevDragView;
+                }
+            }
+        }
+        event.recycle();
+    }
+
+    public void setDragFocus(DragEvent event, View newDragTarget) {
+        final int action = event.mAction;
+        // If we've dragged off of a view, send it the EXITED message
+        if (mCurrentDragView != newDragTarget) {
+            if (mCurrentDragView != null) {
+                event.mAction = DragEvent.ACTION_DRAG_EXITED;
+                mCurrentDragView.dispatchDragEvent(event);
+            }
+        }
+        // If we've dragged over a new view, send it the ENTERED message
+        if (newDragTarget != null) {
+            event.mAction = DragEvent.ACTION_DRAG_ENTERED;
+            newDragTarget.dispatchDragEvent(event);
+        }
+        mCurrentDragView = newDragTarget;
+        event.mAction = action;  // restore the event's original state
+    }
+
     private AudioManager getAudioManager() {
         if (mView == null) {
             throw new IllegalStateException("getAudioManager called when there is no mView");
@@ -2725,7 +2818,12 @@
         msg.obj = reason;
         sendMessage(msg);
     }
-    
+
+    public void dispatchDragEvent(DragEvent event) {
+        Message msg = obtainMessage(DISPATCH_DRAG_EVENT, event);
+        sendMessage(msg);
+    }
+
     /**
      * The window is getting focus so if there is anything focused/selected
      * send an {@link AccessibilityEvent} to announce that.
@@ -2936,6 +3034,14 @@
                 }
             }
         }
+
+        /* Drag/drop */
+        public void dispatchDragEvent(DragEvent event) {
+            final ViewRoot viewRoot = mViewRoot.get();
+            if (viewRoot != null) {
+                viewRoot.dispatchDragEvent(event);
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index dc3b44d..eddd04e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -347,6 +347,13 @@
         public static final int TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14;
         
         /**
+         * Window type: the drag-and-drop pseudowindow.  There is only one
+         * drag layer (at most), and it is placed on top of all other windows.
+         * @hide
+         */
+        public static final int TYPE_DRAG               = FIRST_SYSTEM_WINDOW+15;
+
+        /**
          * End of types of system windows.
          */
         public static final int LAST_SYSTEM_WINDOW      = 2999;
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 7944807..fedb873 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1291,6 +1291,7 @@
     private void clearHelpers() {
         clearTextEntry();
         clearActionModes();
+        dismissFullScreenMode();
     }
 
     /**
@@ -4911,6 +4912,13 @@
         return mFullScreenHolder != null;
     }
 
+    private void dismissFullScreenMode() {
+        if (inFullScreenMode()) {
+            mFullScreenHolder.dismiss();
+            mFullScreenHolder = null;
+        }
+    }
+
     void onPinchToZoomAnimationStart() {
         // cancel the single touch handling
         cancelTouch();
@@ -6878,9 +6886,9 @@
                     View view = (View) msg.obj;
                     int npp = msg.arg1;
 
-                    if (mFullScreenHolder != null) {
+                    if (inFullScreenMode()) {
                         Log.w(LOGTAG, "Should not have another full screen.");
-                        mFullScreenHolder.dismiss();
+                        dismissFullScreenMode();
                     }
                     mFullScreenHolder = new PluginFullScreenHolder(WebView.this, npp);
                     mFullScreenHolder.setContentView(view);
@@ -6891,10 +6899,7 @@
                     break;
                 }
                 case HIDE_FULLSCREEN:
-                    if (inFullScreenMode()) {
-                        mFullScreenHolder.dismiss();
-                        mFullScreenHolder = null;
-                    }
+                    dismissFullScreenMode();
                     break;
 
                 case DOM_FOCUS_CHANGED:
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 1bc0612..6799ffd 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -88,6 +88,7 @@
     private boolean mOutsideTouchable = false;
     private boolean mClippingEnabled = true;
     private boolean mSplitTouchEnabled;
+    private boolean mLayoutInScreen;
 
     private OnTouchListener mTouchInterceptor;
     
@@ -607,6 +608,29 @@
     }
 
     /**
+     * <p>Indicates whether the popup window will be forced into using absolute screen coordinates
+     * for positioning.</p>
+     *
+     * @return true if the window will always be positioned in screen coordinates.
+     * @hide
+     */
+    public boolean isLayoutInScreenEnabled() {
+        return mLayoutInScreen;
+    }
+
+    /**
+     * <p>Allows the popup window to force the flag
+     * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}, overriding default behavior.
+     * This will cause the popup to be positioned in absolute screen coordinates.</p>
+     *
+     * @param enabled true if the popup should always be positioned in screen coordinates
+     * @hide
+     */
+    public void setLayoutInScreenEnabled(boolean enabled) {
+        mLayoutInScreen = enabled;
+    }
+
+    /**
      * <p>Change the width and height measure specs that are given to the
      * window manager by the popup.  By default these are 0, meaning that
      * the current width or height is requested as an explicit size from
@@ -910,7 +934,8 @@
                 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
                 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
                 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
-                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
+                WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);
         if(mIgnoreCheekPress) {
             curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
         }
@@ -934,6 +959,9 @@
         if (mSplitTouchEnabled) {
             curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
         }
+        if (mLayoutInScreen) {
+            curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+        }
         return curFlags;
     }
     
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index f4d193f..5a2cebe 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3798,6 +3798,8 @@
         if (mError != null) {
             hideError();
         }
+
+        hideControllers();
     }
 
     @Override
@@ -4165,6 +4167,15 @@
         */
 
         canvas.restore();
+
+        if (mInsertionPointCursorController != null &&
+                mInsertionPointCursorController.isShowing()) {
+            mInsertionPointCursorController.updatePosition();
+        }
+        if (mSelectionModifierCursorController != null &&
+                mSelectionModifierCursorController.isShowing()) {
+            mSelectionModifierCursorController.updatePosition();
+        }
     }
 
     @Override
@@ -4788,6 +4799,7 @@
         if (mInputMethodState != null) {
             mInputMethodState.mExtracting = req;
         }
+        hideControllers();
     }
     
     /**
@@ -6326,7 +6338,11 @@
         
         sendOnTextChanged(buffer, start, before, after);
         onTextChanged(buffer, start, before, after);
-        hideControllers();
+
+        // Hide the controller if the amount of content changed
+        if (before != after) {
+            hideControllers();
+        }
     }
     
     /**
@@ -6668,11 +6684,20 @@
             if (mInputContentType != null) {
                 mInputContentType.enterDown = false;
             }
+            hideControllers();
         }
 
         startStopMarquee(hasWindowFocus);
     }
 
+    @Override
+    protected void onVisibilityChanged(View changedView, int visibility) {
+        super.onVisibilityChanged(changedView, visibility);
+        if (visibility != VISIBLE) {
+            hideControllers();
+        }
+    }
+
     /**
      * Use {@link BaseInputConnection#removeComposingSpans
      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
@@ -6742,8 +6767,6 @@
 
                 if (hasSelection()) {
                     startSelectionActionMode();
-                } else if (mInsertionPointCursorController != null) {
-                    mInsertionPointCursorController.show();
                 }
             }
         }
@@ -7571,28 +7594,28 @@
 
             if (canSelectAll()) {
                 menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
-                    setIcon(com.android.internal.R.drawable.ic_menu_chat_dashboard).
+                    setIcon(com.android.internal.R.drawable.ic_menu_select_all).
                     setAlphabeticShortcut('a');
                 atLeastOne = true;
             }
 
             if (canCut()) {
                 menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
-                    setIcon(com.android.internal.R.drawable.ic_menu_compose).
+                    setIcon(com.android.internal.R.drawable.ic_menu_cut).
                     setAlphabeticShortcut('x');
                 atLeastOne = true;
             }
 
             if (canCopy()) {
                 menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
-                    setIcon(com.android.internal.R.drawable.ic_menu_attachment).
+                    setIcon(com.android.internal.R.drawable.ic_menu_copy).
                     setAlphabeticShortcut('c');
                 atLeastOne = true;
             }
 
             if (canPaste()) {
                 menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
-                        setIcon(com.android.internal.R.drawable.ic_menu_camera).
+                        setIcon(com.android.internal.R.drawable.ic_menu_paste).
                         setAlphabeticShortcut('v');
                 atLeastOne = true;
             }
@@ -7732,6 +7755,8 @@
         private int mPositionY;
         private CursorController mController;
         private boolean mIsDragging;
+        private int mOffsetX;
+        private int mOffsetY;
 
         public HandleView(CursorController controller, Drawable handle) {
             super(TextView.this.mContext);
@@ -7740,6 +7765,8 @@
             mContainer = new PopupWindow(TextView.this.mContext, null,
                     com.android.internal.R.attr.textSelectHandleWindowStyle);
             mContainer.setSplitTouchEnabled(true);
+            mContainer.setClippingEnabled(false);
+            mContainer.setLayoutInScreenEnabled(true);
         }
 
         @Override
@@ -7777,19 +7804,18 @@
             final int compoundPaddingRight = getCompoundPaddingRight();
 
             final TextView hostView = TextView.this;
-            final int right = hostView.mRight;
-            final int left = hostView.mLeft;
-            final int bottom = hostView.mBottom;
-            final int top = hostView.mTop;
+            final int handleWidth = mDrawable.getIntrinsicWidth();
+            final int left = 0;
+            final int right = hostView.getWidth();
+            final int top = 0;
+            final int bottom = hostView.getHeight();
 
-            final int clipLeft = left + compoundPaddingLeft;
+            final int clipLeft = left + compoundPaddingLeft - (int) (handleWidth * 0.75f);
             final int clipTop = top + extendedPaddingTop;
-            final int clipRight = right - compoundPaddingRight;
+            final int clipRight = right - compoundPaddingRight + (int) (handleWidth * 0.25f);
             final int clipBottom = bottom - extendedPaddingBottom;
 
-            final int handleWidth = mDrawable.getIntrinsicWidth();
-            return mPositionX >= clipLeft - handleWidth * 0.75f &&
-                    mPositionX <= clipRight + handleWidth * 0.25f &&
+            return mPositionX >= clipLeft && mPositionX <= clipRight &&
                     mPositionY >= clipTop && mPositionY <= clipBottom;
         }
 
@@ -7828,6 +7854,8 @@
         public boolean onTouchEvent(MotionEvent ev) {
             switch (ev.getActionMasked()) {
             case MotionEvent.ACTION_DOWN:
+                mOffsetX = (int) (ev.getX() - mDrawable.getIntrinsicWidth() / 2.f + 0.5f);
+                mOffsetY = (int) (ev.getY() - mDrawable.getIntrinsicHeight() / 2.f + 0.5f);
                 mIsDragging = true;
                 break;
 
@@ -7836,8 +7864,10 @@
                 final float rawY = ev.getRawY();
                 final int[] coords = mTempCoords;
                 TextView.this.getLocationOnScreen(coords);
-                final int x = (int) (rawX - coords[0] + 0.5f);
-                final int y = (int) (rawY - coords[1] + 0.5f);
+                final int x = (int) (rawX - coords[0] + 0.5f) - mOffsetX;
+                final int y = (int) (rawY - coords[1] + 0.5f) -
+                        (int) (mDrawable.getIntrinsicHeight() * 0.8f) - mOffsetY;
+
                 mController.updatePosition(this, x, y);
                 break;
 
@@ -8146,7 +8176,7 @@
         final int previousLine = layout.getLineForOffset(previousOffset);
         final int previousLineTop = layout.getLineTop(previousLine);
         final int previousLineBottom = layout.getLineBottom(previousLine);
-        final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 2;
+        final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 6;
 
         // If new line is just before or after previous line and y position is less than
         // hysteresisThreshold away from previous line, keep cursor on previous line.
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index 6e11cff..2a8cd94 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -180,7 +180,7 @@
                 Log.d(TAG, "Found gdbserver: " + entry.getName());
             }
 
-            final String installGdbServerPath = APK_LIB + GDBSERVER;
+            final String installGdbServerPath = GDBSERVER;
             nativeFiles.add(Pair.create(entry, installGdbServerPath));
 
             return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES;
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 4da74e6..d5213db 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -16,11 +16,14 @@
 
 package com.android.internal.view;
 
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.view.DragEvent;
 import android.view.IWindow;
 import android.view.IWindowSession;
 import android.view.KeyEvent;
@@ -66,7 +69,10 @@
             }
         }
     }
-    
+
+    public void dispatchDragEvent(DragEvent event) {
+    }
+
     public void dispatchWallpaperCommand(String action, int x, int y,
             int z, Bundle extras, boolean sync) {
         if (sync) {
diff --git a/core/jni/android/graphics/Matrix.cpp b/core/jni/android/graphics/Matrix.cpp
index b782766..cafceab 100644
--- a/core/jni/android/graphics/Matrix.cpp
+++ b/core/jni/android/graphics/Matrix.cpp
@@ -27,6 +27,8 @@
 #include "SkMatrix.h"
 #include "SkTemplates.h"
 
+#include "Matrix.h"
+
 namespace android {
 
 class SkMatrixGlue {
@@ -403,10 +405,20 @@
     {"native_equals", "(II)Z", (void*) SkMatrixGlue::equals}
 };
 
+static jfieldID sNativeInstanceField;
+
 int register_android_graphics_Matrix(JNIEnv* env) {
     int result = AndroidRuntime::registerNativeMethods(env, "android/graphics/Matrix", methods,
         sizeof(methods) / sizeof(methods[0]));
+
+    jclass clazz = env->FindClass("android/graphics/Matrix");
+    sNativeInstanceField = env->GetFieldID(clazz, "native_instance", "I");
+
     return result;
 }
 
+SkMatrix* android_graphics_Matrix_getSkMatrix(JNIEnv* env, jobject matrixObj) {
+    return reinterpret_cast<SkMatrix*>(env->GetIntField(matrixObj, sNativeInstanceField));
+}
+
 }
diff --git a/core/jni/android/graphics/Matrix.h b/core/jni/android/graphics/Matrix.h
new file mode 100644
index 0000000..31edf88
--- /dev/null
+++ b/core/jni/android/graphics/Matrix.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_GRAPHICS_MATRIX_H
+#define _ANDROID_GRAPHICS_MATRIX_H
+
+#include "jni.h"
+#include "SkMatrix.h"
+
+namespace android {
+
+/* Gets the underlying SkMatrix from a Matrix object. */
+extern SkMatrix* android_graphics_Matrix_getSkMatrix(JNIEnv* env, jobject matrixObj);
+
+} // namespace android
+
+#endif // _ANDROID_GRAPHICS_MATRIX_H
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 93fd54f..537ac72 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -22,10 +22,26 @@
 #include <utils/Log.h>
 #include <ui/Input.h>
 #include "android_view_MotionEvent.h"
+#include "android/graphics/Matrix.h"
+
+#include <math.h>
+#include "SkMatrix.h"
+#include "SkScalar.h"
 
 // Number of float items per entry in a DVM sample data array
 #define NUM_SAMPLE_DATA 9
 
+#define SAMPLE_X 0
+#define SAMPLE_Y 1
+#define SAMPLE_PRESSURE 2
+#define SAMPLE_SIZE 3
+#define SAMPLE_TOUCH_MAJOR 4
+#define SAMPLE_TOUCH_MINOR 5
+#define SAMPLE_TOOL_MAJOR 6
+#define SAMPLE_TOOL_MINOR 7
+#define SAMPLE_ORIENTATION 8
+
+
 namespace android {
 
 // ----------------------------------------------------------------------------
@@ -238,8 +254,87 @@
     }
 }
 
+static inline float transformAngle(const SkMatrix* matrix, float angleRadians) {
+    // Construct and transform a vector oriented at the specified clockwise angle from vertical.
+    // Coordinate system: down is increasing Y, right is increasing X.
+    SkPoint vector;
+    vector.fX = SkFloatToScalar(sinf(angleRadians));
+    vector.fY = SkFloatToScalar(- cosf(angleRadians));
+    matrix->mapVectors(& vector, 1);
+
+    // Derive the transformed vector's clockwise angle from vertical.
+    float result = atan2f(SkScalarToFloat(vector.fX), SkScalarToFloat(- vector.fY));
+    if (result < - M_PI_2) {
+        result += M_PI;
+    } else if (result > M_PI_2) {
+        result -= M_PI;
+    }
+    return result;
+}
+
+static void android_view_MotionEvent_nativeTransform(JNIEnv* env,
+        jobject eventObj, jobject matrixObj) {
+    SkMatrix* matrix = android_graphics_Matrix_getSkMatrix(env, matrixObj);
+
+    jfloat oldXOffset = env->GetFloatField(eventObj, gMotionEventClassInfo.mXOffset);
+    jfloat oldYOffset = env->GetFloatField(eventObj, gMotionEventClassInfo.mYOffset);
+    jint numPointers = env->GetIntField(eventObj, gMotionEventClassInfo.mNumPointers);
+    jint numSamples = env->GetIntField(eventObj, gMotionEventClassInfo.mNumSamples);
+    jfloatArray dataSampleArray = jfloatArray(env->GetObjectField(eventObj,
+            gMotionEventClassInfo.mDataSamples));
+    jfloat* dataSamples = (jfloat*)env->GetPrimitiveArrayCritical(dataSampleArray, NULL);
+
+    // The tricky part of this implementation is to preserve the value of
+    // rawX and rawY.  So we apply the transformation to the first point
+    // then derive an appropriate new X/Y offset that will preserve rawX and rawY.
+    SkPoint point;
+    jfloat rawX = dataSamples[SAMPLE_X];
+    jfloat rawY = dataSamples[SAMPLE_Y];
+    matrix->mapXY(SkFloatToScalar(rawX + oldXOffset), SkFloatToScalar(rawY + oldYOffset),
+            & point);
+    jfloat newX = SkScalarToFloat(point.fX);
+    jfloat newY = SkScalarToFloat(point.fY);
+    jfloat newXOffset = newX - rawX;
+    jfloat newYOffset = newY - rawY;
+
+    dataSamples[SAMPLE_ORIENTATION] = transformAngle(matrix, dataSamples[SAMPLE_ORIENTATION]);
+
+    // Apply the transformation to all samples.
+    jfloat* currentDataSample = dataSamples;
+    jfloat* endDataSample = dataSamples + numPointers * numSamples * NUM_SAMPLE_DATA;
+    for (;;) {
+        currentDataSample += NUM_SAMPLE_DATA;
+        if (currentDataSample == endDataSample) {
+            break;
+        }
+
+        jfloat x = currentDataSample[SAMPLE_X] + oldXOffset;
+        jfloat y = currentDataSample[SAMPLE_Y] + oldYOffset;
+        matrix->mapXY(SkFloatToScalar(x), SkFloatToScalar(y), & point);
+        currentDataSample[SAMPLE_X] = SkScalarToFloat(point.fX) - newXOffset;
+        currentDataSample[SAMPLE_Y] = SkScalarToFloat(point.fY) - newYOffset;
+
+        currentDataSample[SAMPLE_ORIENTATION] = transformAngle(matrix,
+                currentDataSample[SAMPLE_ORIENTATION]);
+    }
+
+    env->ReleasePrimitiveArrayCritical(dataSampleArray, dataSamples, 0);
+
+    env->SetFloatField(eventObj, gMotionEventClassInfo.mXOffset, newXOffset);
+    env->SetFloatField(eventObj, gMotionEventClassInfo.mYOffset, newYOffset);
+
+    env->DeleteLocalRef(dataSampleArray);
+}
+
 // ----------------------------------------------------------------------------
 
+static JNINativeMethod gMotionEventMethods[] = {
+    /* name, signature, funcPtr */
+    { "nativeTransform",
+            "(Landroid/graphics/Matrix;)V",
+            (void*)android_view_MotionEvent_nativeTransform },
+};
+
 #define FIND_CLASS(var, className) \
         var = env->FindClass(className); \
         LOG_FATAL_IF(! var, "Unable to find class " className); \
@@ -258,6 +353,10 @@
         LOG_FATAL_IF(! var, "Unable to find field " fieldName);
 
 int register_android_view_MotionEvent(JNIEnv* env) {
+    int res = jniRegisterNativeMethods(env, "android/view/MotionEvent",
+            gMotionEventMethods, NELEM(gMotionEventMethods));
+    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+
     FIND_CLASS(gMotionEventClassInfo.clazz, "android/view/MotionEvent");
 
     GET_STATIC_METHOD_ID(gMotionEventClassInfo.obtain, gMotionEventClassInfo.clazz,
diff --git a/core/res/res/drawable-hdpi/ic_menu_copy.png b/core/res/res/drawable-hdpi/ic_menu_copy.png
new file mode 100644
index 0000000..8f11153
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_menu_copy.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_cut.png b/core/res/res/drawable-hdpi/ic_menu_cut.png
new file mode 100644
index 0000000..6ad379e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_menu_cut.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_paste.png b/core/res/res/drawable-hdpi/ic_menu_paste.png
new file mode 100644
index 0000000..5a3850f
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_menu_paste.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_select_all.png b/core/res/res/drawable-hdpi/ic_menu_select_all.png
new file mode 100644
index 0000000..dde6741
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_menu_select_all.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_copy.png b/core/res/res/drawable-mdpi/ic_menu_copy.png
new file mode 100644
index 0000000..89d626f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_menu_copy.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_cut.png b/core/res/res/drawable-mdpi/ic_menu_cut.png
new file mode 100644
index 0000000..1b4733e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_menu_cut.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_paste.png b/core/res/res/drawable-mdpi/ic_menu_paste.png
new file mode 100755
index 0000000..cdf7ca3
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_menu_paste.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_select_all.png b/core/res/res/drawable-mdpi/ic_menu_select_all.png
new file mode 100644
index 0000000..37fd3cbd
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_menu_select_all.png
Binary files differ
diff --git a/core/res/res/menu/webview_copy.xml b/core/res/res/menu/webview_copy.xml
index 224f54f..adba563 100644
--- a/core/res/res/menu/webview_copy.xml
+++ b/core/res/res/menu/webview_copy.xml
@@ -16,7 +16,7 @@
 
 <menu xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:id="@+id/copy"
-        android:icon="@drawable/ic_menu_attachment"
+        android:icon="@drawable/ic_menu_copy"
         android:showAsAction="always"
         />
     <item android:id="@+id/share"
@@ -24,7 +24,7 @@
         android:showAsAction="always"
         />
     <item android:id="@+id/select_all"
-        android:icon="@drawable/ic_menu_chat_dashboard"
+        android:icon="@drawable/ic_menu_select_all"
         android:showAsAction="always"
         />
     <item android:id="@+id/find"
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index b618756..830fb01 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -71,20 +71,6 @@
         <item>@drawable/indicator_code_lock_point_area_default</item>
         <item>@drawable/indicator_code_lock_point_area_green</item>
         <item>@drawable/indicator_code_lock_point_area_red</item>
-        <!-- SlidingTab drawables shared by InCallScreen and LockScreen -->
-        <item>@drawable/jog_tab_bar_left_end_confirm_gray</item>
-        <item>@drawable/jog_tab_bar_left_end_normal</item>
-        <item>@drawable/jog_tab_bar_left_end_pressed</item>
-        <item>@drawable/jog_tab_bar_right_end_confirm_gray</item>
-        <item>@drawable/jog_tab_bar_right_end_normal</item>
-        <item>@drawable/jog_tab_bar_right_end_pressed</item>
-        <item>@drawable/jog_tab_left_confirm_gray</item>
-        <item>@drawable/jog_tab_left_normal</item>
-        <item>@drawable/jog_tab_left_pressed</item>
-        <item>@drawable/jog_tab_right_confirm_gray</item>
-        <item>@drawable/jog_tab_right_normal</item>
-        <item>@drawable/jog_tab_right_pressed</item>
-        <item>@drawable/jog_tab_target_gray</item>
     </array>
 
     <!-- Do not translate. These are all of the color state list resources that should be
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
old mode 100644
new mode 100755
index 7a9b59a..8b4f91f
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -387,8 +387,10 @@
 
     <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permgrouplab_storage">Storage</string>
+    <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this.   [CHAR LIMIT=30] -->
+    <string name="permgroupdesc_storage" product="nosdcard">Access the shared storage.</string>
     <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permgroupdesc_storage">Access the SD card.</string>
+    <string name="permgroupdesc_storage" product="default">Access the SD card.</string>
 
     <!--  Permissions -->
 
@@ -1230,10 +1232,14 @@
     <string name="permdesc_writeDictionary">Allows an application to write new words into the
       user dictionary.</string>
 
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=30] -->
+    <string name="permlab_sdcardWrite" product="nosdcard">modify/delete shared storage contents</string>
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permlab_sdcardWrite">modify/delete SD card contents</string>
+    <string name="permlab_sdcardWrite" product="default">modify/delete SD card contents</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=30] -->
+    <string name="permdesc_sdcardWrite" product="nosdcard">Allows an application to write to the shared storage.</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_sdcardWrite">Allows an application to write to the SD card.</string>
+    <string name="permdesc_sdcardWrite" product="default">Allows an application to write to the SD card.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_cache_filesystem">access the cache filesystem</string>
@@ -1243,31 +1249,29 @@
     <!-- Policy administration -->
 
     <!-- Title of policy access to limiting the user's password choices -->
-    <string name="policylab_limitPassword">Limit password</string>
+    <string name="policylab_limitPassword">Set password rules</string>
     <!-- Description of policy access to limiting the user's password choices -->
-    <string name="policydesc_limitPassword">Restrict the types of passwords you
-        are allowed to use.</string>
+    <string name="policydesc_limitPassword">Control the length and the characters 
+    allowed in screen-unlock passwords</string>
     <!-- Title of policy access to watch user login attempts -->
-    <string name="policylab_watchLogin">Watch login attempts</string>
+    <string name="policylab_watchLogin">Monitor screen-unlock attempts</string>
     <!-- Description of policy access to watch user login attempts -->
-    <string name="policydesc_watchLogin">Monitor failed attempts to login to
-        the device, to perform some action.</string>
+    <string name="policydesc_watchLogin">Monitor the number of incorrect passwords 
+    entered when unlocking the screen, and lock the phone or erase all the phone\'s 
+    data if too many incorrect passwords are entered</string>
     <!-- Title of policy access to reset user's password -->
-    <string name="policylab_resetPassword">Reset password</string>
+    <string name="policylab_resetPassword">Change the screen-unlock password</string>
     <!-- Description of policy access to reset user's password -->
-    <string name="policydesc_resetPassword">Force your password
-        to a new value, requiring the administrator give it to you
-        before you can log in.</string>
+    <string name="policydesc_resetPassword">Change the screen-unlock password</string>
     <!-- Title of policy access to force lock the device -->
-    <string name="policylab_forceLock">Force lock</string>
+    <string name="policylab_forceLock">Lock the screen</string>
     <!-- Description of policy access to limiting the user's password choices -->
-    <string name="policydesc_forceLock">Control when device locks,
-        requiring you re-enter its password.</string>
+    <string name="policydesc_forceLock">Control how and when the screen locks</string>
     <!-- Title of policy access to wipe the user's data -->
     <string name="policylab_wipeData">Erase all data</string>
     <!-- Description of policy access to wipe the user's data -->
-    <string name="policydesc_wipeData">Perform a factory reset, deleting
-        all of your data without any confirmation.</string>
+    <string name="policydesc_wipeData">Erase the phone\'s data without warning, 
+    by performing a factory data reset</string>
     <string name="policylab_setGlobalProxy">Set the device global proxy</string>
     <!-- Description of policy access to wipe the user's data -->
     <string name="policydesc_setGlobalProxy">Set the device global proxy
@@ -2115,12 +2119,16 @@
 
     <!-- See USB_STORAGE.  USB_STORAGE_DIALOG:  After the user selects the notification, a dialog is shown asking if he wants to mount.  This is the title. -->
     <string name="usb_storage_title">USB connected</string>
+    <!-- See USB_STORAGE.    This is the message. [CHAR LIMIT=NONE] -->
+    <string name="usb_storage_message" product="nosdcard">You have connected your phone to your computer via USB. Select the button below if you want to copy files between your computer and your Android\u2018s shared storage.</string>
     <!-- See USB_STORAGE.    This is the message. -->
-    <string name="usb_storage_message">You have connected your phone to your computer via USB. Select the button below if you want to copy files between your computer and your Android\u2018s SD card.</string>
+    <string name="usb_storage_message" product="default">You have connected your phone to your computer via USB. Select the button below if you want to copy files between your computer and your Android\u2018s SD card.</string>
     <!-- See USB_STORAGE.    This is the button text to mount the phone on the computer. -->
     <string name="usb_storage_button_mount">Turn on USB storage</string>
+    <!-- See USB_STORAGE_DIALOG.  If there was an error mounting, this is the text. [CHAR LIMIT=NONE] -->
+    <string name="usb_storage_error_message" product="nosdcard">There is a problem using your shared storage for USB storage.</string>
     <!-- See USB_STORAGE_DIALOG.  If there was an error mounting, this is the text. -->
-    <string name="usb_storage_error_message">There is a problem using your SD card for USB storage.</string>
+    <string name="usb_storage_error_message" product="default">There is a problem using your SD card for USB storage.</string>
     <!-- USB_STORAGE: When the user connects the phone to a computer via USB, we show a notification asking if he wants to share files across.  This is the title -->
     <string name="usb_storage_notification_title">USB connected</string>
     <!-- See USB_STORAGE. This is the message. -->
@@ -2135,8 +2143,10 @@
     <!-- This is the label for the activity, and should never be visible to the user. -->
     <!-- See USB_STORAGE_STOP.  USB_STORAGE_STOP_DIALOG:  After the user selects the notification, a dialog is shown asking if he wants to stop usb storage.  This is the title. -->
     <string name="usb_storage_stop_title">USB storage in use</string>
+    <!-- See USB_STORAGE_STOP.    This is the message. [CHAR LIMIT=NONE] -->
+    <string name="usb_storage_stop_message" product="nosdcard">Before turning off USB storage, make sure you have unmounted (\u201cejected\u201d) your Android\u2018s shared storage from your computer.</string>
     <!-- See USB_STORAGE_STOP.    This is the message. -->
-    <string name="usb_storage_stop_message">Before turning off USB storage, make sure you have unmounted (\u201cejected\u201d) your Android\u2018s SD card from your computer.</string>
+    <string name="usb_storage_stop_message" product="default">Before turning off USB storage, make sure you have unmounted (\u201cejected\u201d) your Android\u2018s SD card from your computer.</string>
     <!-- See USB_STORAGE_STOP.    This is the button text to stop usb storage. -->
     <string name="usb_storage_stop_button_mount">Turn off USB storage</string>
     <!-- See USB_STORAGE_STOP_DIALOG.  If there was an error stopping, this is the text. -->
@@ -2153,10 +2163,14 @@
 
     <!-- External media format dialog strings -->
     <!-- This is the label for the activity, and should never be visible to the user. -->
+    <!-- See EXTMEDIA_FORMAT.  EXTMEDIA_FORMAT_DIALOG:  After the user selects the notification, a dialog is shown asking if he wants to format the SD card.  This is the title. [CHAR LIMIT=20] -->
+    <string name="extmedia_format_title" product="nosdcard">Format shared storage</string>
     <!-- See EXTMEDIA_FORMAT.  EXTMEDIA_FORMAT_DIALOG:  After the user selects the notification, a dialog is shown asking if he wants to format the SD card.  This is the title. -->
-    <string name="extmedia_format_title">Format SD card</string>
+    <string name="extmedia_format_title" product="default">Format SD card</string>
+    <!-- See EXTMEDIA_FORMAT.   This is the message. [CHAR LIMIT=NONE] -->
+    <string name="extmedia_format_message" product="nosdcard">Format shared storage, erasing all files stored there?  Action cannot be reversed!</string>
     <!-- See EXTMEDIA_FORMAT.   This is the message. -->
-    <string name="extmedia_format_message">Are you sure you want to format the SD card? All data on your card will be lost.</string>
+    <string name="extmedia_format_message" product="default">Are you sure you want to format the SD card? All data on your card will be lost.</string>
     <!-- See EXTMEDIA_FORMAT.    This is the button text to format the sd card. -->
     <string name="extmedia_format_button_format">Format</string>
 
@@ -2181,29 +2195,51 @@
     <string name="candidates_style"><u>candidates</u></string>
 
     <!-- External media notification strings -->
+    <!-- Shown when external media is being checked [CHAR LIMIT=30] -->
+    <string name="ext_media_checking_notification_title" product="nosdcard">Preparing shared storage</string>
     <!-- Shown when external media is being checked -->
-    <string name="ext_media_checking_notification_title">Preparing SD card</string>
+    <string name="ext_media_checking_notification_title" product="default">Preparing SD card</string>
     <string name="ext_media_checking_notification_message">Checking for errors.</string>
 
+    <!-- Shown when external media is blank (or unsupported filesystem) [CHAR LIMIT=30] -->
+    <string name="ext_media_nofs_notification_title" product="nosdcard">Blank shared storage</string>
     <!-- Shown when external media is blank (or unsupported filesystem) -->
-    <string name="ext_media_nofs_notification_title">Blank SD card</string>
-    <string name="ext_media_nofs_notification_message">SD card blank or has unsupported filesystem.</string>
+    <string name="ext_media_nofs_notification_title" product="default">Blank SD card</string>
+    <!-- Shown when shared storage cannot be read.  [CHAR LIMIT=NONE] -->
+    <string name="ext_media_nofs_notification_message" product="nosdcard">Shared storage blank or has unsupported filesystem.</string>
+    <string name="ext_media_nofs_notification_message" product="default">SD card blank or has unsupported filesystem.</string>
 
+    <!-- Shown when external media is unmountable (corrupt)) [CHAR LIMIT=30] -->
+    <string name="ext_media_unmountable_notification_title" product="nosdcard">Damaged shared storage</string>
     <!-- Shown when external media is unmountable (corrupt)) -->
-    <string name="ext_media_unmountable_notification_title">Damaged SD card</string>
-    <string name="ext_media_unmountable_notification_message">SD card damaged. You may have to reformat it.</string>
+    <string name="ext_media_unmountable_notification_title" product="default">Damaged SD card</string>
+    <!-- Shown when shared storage cannot be read.  [CHAR LIMIT=NONE] -->
+    <string name="ext_media_unmountable_notification_message" product="nosdcard">Shared storage damaged. You may have to reformat it.</string>
+    <string name="ext_media_unmountable_notification_message" product="default">SD card damaged. You may have to reformat it.</string>
 
+    <!-- Shown when external media is unsafely removed [CHAR LIMIT=30] -->
+    <string name="ext_media_badremoval_notification_title" product="nosdcard">Shared storage unexpectedly removed</string>
     <!-- Shown when external media is unsafely removed -->
-    <string name="ext_media_badremoval_notification_title">SD card unexpectedly removed</string>
-    <string name="ext_media_badremoval_notification_message">Unmount SD card before removing to avoid data loss.</string>
+    <string name="ext_media_badremoval_notification_title" product="default">SD card unexpectedly removed</string>
+    <!-- Shown when external media is unsafely removed.  [CHAR LIMIT=NONE] -->
+    <string name="ext_media_badremoval_notification_message" product="nosdcard">Unmount shared storage before removing to avoid data loss.</string>
+    <string name="ext_media_badremoval_notification_message" product="default">Unmount SD card before removing to avoid data loss.</string>
 
+    <!-- Shown when external media has been safely removed [CHAR LIMIT=30] -->
+    <string name="ext_media_safe_unmount_notification_title" product="nosdcard">Shared storage safe to remove</string>
     <!-- Shown when external media has been safely removed -->
-    <string name="ext_media_safe_unmount_notification_title">SD card safe to remove</string>
-    <string name="ext_media_safe_unmount_notification_message">You can safely remove SD card.</string>
+    <string name="ext_media_safe_unmount_notification_title" product="default">SD card safe to remove</string>
+    <!-- Shown when external media has been safely removed.  [CHAR LIMIT=NONE] -->
+    <string name="ext_media_safe_unmount_notification_message" product="nosdcard">You can safely remove shared storage.</string>
+    <string name="ext_media_safe_unmount_notification_message" product="default">You can safely remove SD card.</string>
 
+    <!-- Shown when external media is missing [CHAR LIMIT=30] -->
+    <string name="ext_media_nomedia_notification_title" product="nosdcard">Removed shared storage</string>
     <!-- Shown when external media is missing -->
-    <string name="ext_media_nomedia_notification_title">Removed SD card</string>
-    <string name="ext_media_nomedia_notification_message">SD card removed. Insert a new one.</string>
+    <string name="ext_media_nomedia_notification_title" product="default">Removed SD card</string>
+    <!-- Shown when external media is missing.  [CHAR LIMIT=NONE] -->
+    <string name="ext_media_nomedia_notification_message" product="nosdcard">Shared storage removed. Insert new media.</string>
+    <string name="ext_media_nomedia_notification_message" product="default">SD card removed. Insert a new one.</string>
 
     <!-- Shown in LauncherActivity when the requested target Intent didn't return any matching Activities, leaving the list empty. -->
     <string name="activity_list_empty">No matching activities found</string>
diff --git a/core/tests/coretests/src/android/app/DownloadManagerIntegrationTest.java b/core/tests/coretests/src/android/app/DownloadManagerIntegrationTest.java
index 38f336e..27eea4d 100644
--- a/core/tests/coretests/src/android/app/DownloadManagerIntegrationTest.java
+++ b/core/tests/coretests/src/android/app/DownloadManagerIntegrationTest.java
@@ -120,7 +120,7 @@
 
         Cursor cursor = getCursor(dlRequest);
         try {
-            verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE, error);
+            verifyInt(cursor, DownloadManager.COLUMN_REASON, error);
         } finally {
             cursor.close();
         }
@@ -183,7 +183,7 @@
         Cursor cursor = getCursor(dlRequest);
         try {
             verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED);
-            verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+            verifyInt(cursor, DownloadManager.COLUMN_REASON,
                     DownloadManager.ERROR_CANNOT_RESUME);
         } finally {
             cursor.close();
@@ -269,7 +269,7 @@
 
             try {
                 verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED);
-                verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+                verifyInt(cursor, DownloadManager.COLUMN_REASON,
                         DownloadManager.ERROR_FILE_ERROR);
             } finally {
                 cursor.close();
@@ -476,7 +476,7 @@
         // For the last download we should have failed b/c there is not enough space left in cache
         Cursor cursor = getCursor(dlRequest);
         try {
-            verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+            verifyInt(cursor, DownloadManager.COLUMN_REASON,
                     DownloadManager.ERROR_INSUFFICIENT_SPACE);
         } finally {
             cursor.close();
diff --git a/core/tests/coretests/src/android/app/DownloadManagerStressTest.java b/core/tests/coretests/src/android/app/DownloadManagerStressTest.java
index 4ff0295..ddf138f 100644
--- a/core/tests/coretests/src/android/app/DownloadManagerStressTest.java
+++ b/core/tests/coretests/src/android/app/DownloadManagerStressTest.java
@@ -145,7 +145,7 @@
 
             cursor = getCursor(dlRequest);
             verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED);
-            verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+            verifyInt(cursor, DownloadManager.COLUMN_REASON,
                     DownloadManager.ERROR_INSUFFICIENT_SPACE);
         } finally {
             if (cursor != null) {
diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java
index 1beba53..e111662 100644
--- a/core/tests/coretests/src/android/text/TextUtilsTest.java
+++ b/core/tests/coretests/src/android/text/TextUtilsTest.java
@@ -30,7 +30,6 @@
 import android.text.util.Rfc822Tokenizer;
 import android.test.MoreAsserts;
 
-import com.android.common.Rfc822Validator;
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 
@@ -238,39 +237,6 @@
         }
     }
 
-    //==============================================================================================
-    // Email validator
-    //==============================================================================================
-    
-    @SmallTest
-    public void testEmailValidator() {
-        Rfc822Validator validator = new Rfc822Validator("gmail.com");
-        String[] validEmails = new String[] {
-            "a@b.com", "a@b.fr", "a+b@c.com", "a@b.info",
-        };
-        
-        for (String email : validEmails) {
-            assertTrue(email + " should be a valid email address", validator.isValid(email));
-        }
-        
-        String[] invalidEmails = new String[] {
-            "a", "a@b", "a b", "a@b.12"
-        };
-
-        for (String email : invalidEmails) {
-            assertFalse(email + " should not be a valid email address", validator.isValid(email));
-        }
-        
-        Map<String, String> fixes = Maps.newHashMap();
-        fixes.put("a", "<a@gmail.com>");
-        fixes.put("a b", "<ab@gmail.com>");
-        fixes.put("a@b", "<a@b>");
-        
-        for (Map.Entry<String, String> e : fixes.entrySet()) {
-            assertEquals(e.getValue(), validator.fixText(e.getKey()).toString());
-        }
-    }
-
     @SmallTest
     public void testRfc822TokenizerFullAddress() {
         Rfc822Token[] tokens = Rfc822Tokenizer.tokenize("Foo Bar (something) <foo@google.com>");
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index c3416a0..1324431 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -236,6 +236,7 @@
      * @param y The y-coordinate of the end of a line
      */
     public void lineTo(float x, float y) {
+        isSimplePath = false;
         native_lineTo(mNativePath, x, y);
     }
 
@@ -250,6 +251,7 @@
      *           this contour, to specify a line
      */
     public void rLineTo(float dx, float dy) {
+        isSimplePath = false;
         native_rLineTo(mNativePath, dx, dy);
     }
 
diff --git a/graphics/java/android/renderscript/Element.java b/graphics/java/android/renderscript/Element.java
index 05b2d60..dd9fa15 100644
--- a/graphics/java/android/renderscript/Element.java
+++ b/graphics/java/android/renderscript/Element.java
@@ -39,7 +39,7 @@
     public enum DataType {
         //FLOAT_16 (1, 2),
         FLOAT_32 (2, 4),
-        //FLOAT_64 (3, 8),
+        FLOAT_64 (3, 8),
         SIGNED_8 (4, 1),
         SIGNED_16 (5, 2),
         SIGNED_32 (6, 4),
@@ -149,6 +149,13 @@
         return rs.mElement_F32;
     }
 
+    public static Element F64(RenderScript rs) {
+        if(rs.mElement_F64 == null) {
+            rs.mElement_F64 = createUser(rs, DataType.FLOAT_64);
+        }
+        return rs.mElement_F64;
+    }
+
     public static Element ELEMENT(RenderScript rs) {
         if(rs.mElement_ELEMENT == null) {
             rs.mElement_ELEMENT = createUser(rs, DataType.RS_ELEMENT);
diff --git a/graphics/java/android/renderscript/FieldPacker.java b/graphics/java/android/renderscript/FieldPacker.java
index b6f88be..ff3e22b 100644
--- a/graphics/java/android/renderscript/FieldPacker.java
+++ b/graphics/java/android/renderscript/FieldPacker.java
@@ -124,7 +124,7 @@
         addI32(Float.floatToRawIntBits(v));
     }
 
-    public void addF64(float v) {
+    public void addF64(double v) {
         addI64(Double.doubleToRawLongBits(v));
     }
 
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index c4421c3..c952d79 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -388,6 +388,10 @@
     synchronized void nSamplerSet(int param, int value) {
         rsnSamplerSet(mContext, param, value);
     }
+    native void rsnSamplerSet2(int con, int param, float value);
+    synchronized void nSamplerSet2(int param, float value) {
+        rsnSamplerSet2(mContext, param, value);
+    }
     native int  rsnSamplerCreate(int con);
     synchronized int nSamplerCreate() {
         return rsnSamplerCreate(mContext);
@@ -498,6 +502,7 @@
     Element mElement_U32;
     Element mElement_I32;
     Element mElement_F32;
+    Element mElement_F64;
     Element mElement_BOOLEAN;
 
     Element mElement_ELEMENT;
diff --git a/graphics/java/android/renderscript/Sampler.java b/graphics/java/android/renderscript/Sampler.java
index 343fcdb..b627207 100644
--- a/graphics/java/android/renderscript/Sampler.java
+++ b/graphics/java/android/renderscript/Sampler.java
@@ -130,6 +130,7 @@
         Value mWrapS;
         Value mWrapT;
         Value mWrapR;
+        float mAniso;
 
         public Builder(RenderScript rs) {
             mRS = rs;
@@ -138,6 +139,7 @@
             mWrapS = Value.WRAP;
             mWrapT = Value.WRAP;
             mWrapR = Value.WRAP;
+            mAniso = 1.0f;
         }
 
         public void setMin(Value v) {
@@ -182,6 +184,14 @@
             }
         }
 
+        public void setAnisotropy(float v) {
+            if(v >= 0.0f) {
+                mAniso = v;
+            } else {
+                throw new IllegalArgumentException("Invalid value");
+            }
+        }
+
         static synchronized Sampler internalCreate(RenderScript rs, Builder b) {
             rs.nSamplerBegin();
             rs.nSamplerSet(0, b.mMin.mID);
@@ -189,6 +199,7 @@
             rs.nSamplerSet(2, b.mWrapS.mID);
             rs.nSamplerSet(3, b.mWrapT.mID);
             rs.nSamplerSet(4, b.mWrapR.mID);
+            rs.nSamplerSet2(5, b.mAniso);
             int id = rs.nSamplerCreate();
             return new Sampler(id, rs);
         }
diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp
index 6aed11b..67a2b63 100644
--- a/graphics/jni/android_renderscript_RenderScript.cpp
+++ b/graphics/jni/android_renderscript_RenderScript.cpp
@@ -1096,6 +1096,13 @@
     rsSamplerSet(con, (RsSamplerParam)p, (RsSamplerValue)v);
 }
 
+static void
+nSamplerSet2(JNIEnv *_env, jobject _this, RsContext con, jint p, jfloat v)
+{
+    LOG_API("nSamplerSet2, con(%p), param(%i), value(%f)", con, p, v);
+    rsSamplerSet2(con, (RsSamplerParam)p, v);
+}
+
 static jint
 nSamplerCreate(JNIEnv *_env, jobject _this, RsContext con)
 {
@@ -1303,6 +1310,7 @@
 
 {"rsnSamplerBegin",                  "(I)V",                                  (void*)nSamplerBegin },
 {"rsnSamplerSet",                    "(III)V",                                (void*)nSamplerSet },
+{"rsnSamplerSet2",                   "(IIF)V",                                (void*)nSamplerSet2 },
 {"rsnSamplerCreate",                 "(I)I",                                  (void*)nSamplerCreate },
 
 {"rsnMeshCreate",                    "(III)I",                                (void*)nMeshCreate },
diff --git a/include/private/media/AudioTrackShared.h b/include/private/media/AudioTrackShared.h
index 1510f87..c6990bf 100644
--- a/include/private/media/AudioTrackShared.h
+++ b/include/private/media/AudioTrackShared.h
@@ -42,8 +42,11 @@
 #define CBLK_FORCEREADY_ON      0x0004  // track is considered ready immediately by AudioFlinger
 #define CBLK_FORCEREADY_OFF     0x0000  // track is ready when buffer full
 #define CBLK_INVALID_MSK        0x0008
-#define CBLK_INVALID_ON         0x0008  // track buffer is invalidated by AudioFlinger: must be re-created
-#define CBLK_INVALID_OFF        0x0000
+#define CBLK_INVALID_ON         0x0008  // track buffer is invalidated by AudioFlinger:
+#define CBLK_INVALID_OFF        0x0000  // must be re-created
+#define CBLK_DISABLED_MSK       0x0010
+#define CBLK_DISABLED_ON        0x0010  // track disabled by AudioFlinger due to underrun:
+#define CBLK_DISABLED_OFF       0x0000  // must be re-started
 
 struct audio_track_cblk_t
 {
diff --git a/include/surfaceflinger/ISurfaceComposer.h b/include/surfaceflinger/ISurfaceComposer.h
index 76307b2..6533600 100644
--- a/include/surfaceflinger/ISurfaceComposer.h
+++ b/include/surfaceflinger/ISurfaceComposer.h
@@ -115,7 +115,8 @@
      */
     virtual status_t captureScreen(DisplayID dpy,
             sp<IMemoryHeap>* heap,
-            uint32_t* width, uint32_t* height, PixelFormat* format) = 0;
+            uint32_t* width, uint32_t* height, PixelFormat* format,
+            uint32_t reqWidth, uint32_t reqHeight) = 0;
 
     /* Signal surfaceflinger that there might be some work to do
      * This is an ASYNCHRONOUS call.
diff --git a/include/surfaceflinger/SurfaceComposerClient.h b/include/surfaceflinger/SurfaceComposerClient.h
index 8773d71..a80832d 100644
--- a/include/surfaceflinger/SurfaceComposerClient.h
+++ b/include/surfaceflinger/SurfaceComposerClient.h
@@ -170,6 +170,36 @@
 };
 
 // ---------------------------------------------------------------------------
+
+class ScreenshotClient
+{
+    sp<IMemoryHeap> mHeap;
+    uint32_t mWidth;
+    uint32_t mHeight;
+    PixelFormat mFormat;
+public:
+    ScreenshotClient();
+
+    // frees the previous screenshot and capture a new one
+    status_t update();
+    status_t update(uint32_t reqWidth, uint32_t reqHeight);
+
+    // release memory occupied by the screenshot
+    void release();
+
+    // pixels are valid until this object is freed or
+    // release() or update() is called
+    void const* getPixels() const;
+
+    uint32_t getWidth() const;
+    uint32_t getHeight() const;
+    PixelFormat getFormat() const;
+    uint32_t getStride() const;
+    // size of allocated memory in bytes
+    size_t getSize() const;
+};
+
+// ---------------------------------------------------------------------------
 }; // namespace android
 
 #endif // ANDROID_SF_SURFACE_COMPOSER_CLIENT_H
diff --git a/include/ui/InputReader.h b/include/ui/InputReader.h
index e85735a..d3fbaf7 100644
--- a/include/ui/InputReader.h
+++ b/include/ui/InputReader.h
@@ -308,9 +308,6 @@
             GetStateFunc getStateFunc);
     bool markSupportedKeyCodes(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
             const int32_t* keyCodes, uint8_t* outFlags);
-
-    // dump state
-    void dumpDeviceInfo(String8& dump);
 };
 
 
@@ -340,6 +337,7 @@
 
     inline bool isIgnored() { return mMappers.isEmpty(); }
 
+    void dump(String8& dump);
     void addMapper(InputMapper* mapper);
     void configure();
     void reset();
@@ -393,6 +391,7 @@
 
     virtual uint32_t getSources() = 0;
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void dump(String8& dump);
     virtual void configure();
     virtual void reset();
     virtual void process(const RawEvent* rawEvent) = 0;
@@ -436,6 +435,7 @@
 
     virtual uint32_t getSources();
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void dump(String8& dump);
     virtual void reset();
     virtual void process(const RawEvent* rawEvent);
 
@@ -484,6 +484,7 @@
 
     virtual uint32_t getSources();
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void dump(String8& dump);
     virtual void reset();
     virtual void process(const RawEvent* rawEvent);
 
@@ -540,6 +541,7 @@
 
     virtual uint32_t getSources();
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void dump(String8& dump);
     virtual void configure();
     virtual void reset();
 
@@ -761,15 +763,16 @@
     } mLocked;
 
     virtual void configureParameters();
-    virtual void logParameters();
+    virtual void dumpParameters(String8& dump);
     virtual void configureRawAxes();
-    virtual void logRawAxes();
+    virtual void dumpRawAxes(String8& dump);
     virtual bool configureSurfaceLocked();
-    virtual void logMotionRangesLocked();
+    virtual void dumpSurfaceLocked(String8& dump);
     virtual void configureVirtualKeysLocked();
+    virtual void dumpVirtualKeysLocked(String8& dump);
     virtual void parseCalibration();
     virtual void resolveCalibration();
-    virtual void logCalibration();
+    virtual void dumpCalibration(String8& dump);
 
     enum TouchResult {
         // Dispatch the touch normally.
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index 60523db..a0cc5d6 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -44,7 +44,7 @@
     uint32_t id;
 
     bool operator<(const LayerSize& rhs) const {
-        if (id != 0 && rhs.id != 0) {
+        if (id != 0 && rhs.id != 0 && id != rhs.id) {
             return id < rhs.id;
         }
         if (width == rhs.width) {
@@ -54,7 +54,7 @@
     }
 
     bool operator==(const LayerSize& rhs) const {
-        return width == rhs.width && height == rhs.height;
+        return id == rhs.id && width == rhs.width && height == rhs.height;
     }
 }; // struct LayerSize
 
@@ -83,7 +83,7 @@
      */
     bool blend;
     /**
-     * Indicates that this layer has never been used before.
+     * Indicates whether this layer has been used already.
      */
     bool empty;
 }; // struct Layer
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index 2770868..8c70cf9 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -114,6 +114,8 @@
         glGenTextures(1, &layer->texture);
         glBindTexture(GL_TEXTURE_2D, layer->texture);
 
+        glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 23de3a5..0810fb8 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -366,6 +366,8 @@
         return false;
     }
 
+    glActiveTexture(GL_TEXTURE0);
+
     LayerSize size(bounds.getWidth(), bounds.getHeight());
     Layer* layer = mCaches.layerCache.get(size);
     if (!layer) {
@@ -383,17 +385,22 @@
     // Copy the framebuffer into the layer
     glBindTexture(GL_TEXTURE_2D, layer->texture);
 
-    if (layer->empty) {
-        glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
-                bounds.getWidth(), bounds.getHeight(), 0);
-        layer->empty = false;
-    } else {
-        glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left, mHeight - bounds.bottom,
-                bounds.getWidth(), bounds.getHeight());
-    }
+    // TODO: Workaround for b/3054204
+    glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
+            bounds.getWidth(), bounds.getHeight(), 0);
 
-    if (flags & SkCanvas::kClipToLayer_SaveFlag) {
-        if (mSnapshot->clipTransformed(bounds)) setScissorFromClip();
+    // TODO: Waiting for b/3054204 to be fixed
+//    if (layer->empty) {
+//        glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
+//                bounds.getWidth(), bounds.getHeight(), 0);
+//        layer->empty = false;
+//    } else {
+//        glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left, mHeight - bounds.bottom,
+//                bounds.getWidth(), bounds.getHeight());
+//    }
+
+    if (flags & SkCanvas::kClipToLayer_SaveFlag && mSnapshot->clipTransformed(bounds)) {
+        setScissorFromClip();
     }
 
     // Enqueue the buffer coordinates to clear the corresponding region later
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 70e06a1..377727b 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -138,8 +138,8 @@
         const SkPath *path, const SkPaint* paint) {
     const SkRect& bounds = path->getBounds();
 
-    const float pathWidth = bounds.width();
-    const float pathHeight = bounds.height();
+    const float pathWidth = fmax(bounds.width(), 1.0f);
+    const float pathHeight = fmax(bounds.height(), 1.0f);
 
     if (pathWidth > mMaxTextureSize || pathHeight > mMaxTextureSize) {
         LOGW("Path too large to be rendered into a texture");
diff --git a/libs/rs/RenderScript.h b/libs/rs/RenderScript.h
index 7902b9a..13ae1fb 100644
--- a/libs/rs/RenderScript.h
+++ b/libs/rs/RenderScript.h
@@ -122,7 +122,8 @@
     RS_SAMPLER_MAG_FILTER,
     RS_SAMPLER_WRAP_S,
     RS_SAMPLER_WRAP_T,
-    RS_SAMPLER_WRAP_R
+    RS_SAMPLER_WRAP_R,
+    RS_SAMPLER_ANISO
 };
 
 enum RsSamplerValue {
diff --git a/libs/rs/java/ImageProcessing/src/com/android/rs/image/threshold.rs b/libs/rs/java/ImageProcessing/src/com/android/rs/image/threshold.rs
index 33945a5..f5fecba 100644
--- a/libs/rs/java/ImageProcessing/src/com/android/rs/image/threshold.rs
+++ b/libs/rs/java/ImageProcessing/src/com/android/rs/image/threshold.rs
@@ -65,7 +65,6 @@
 
 
 static void copyInput() {
-    RS_DEBUG_MARKER;
     rs_allocation ain = rsGetAllocation(InPixel);
     uint32_t dimx = rsAllocationGetDimX(ain);
     uint32_t dimy = rsAllocationGetDimY(ain);
@@ -74,7 +73,6 @@
             ScratchPixel1[x + y * dimx] = convert_float4(InPixel[x + y * dimx]);
         }
     }
-    RS_DEBUG_MARKER;
 }
 
 void filter() {
diff --git a/libs/rs/java/Samples/res/drawable/checker.png b/libs/rs/java/Samples/res/drawable/checker.png
new file mode 100644
index 0000000..b631e1e
--- /dev/null
+++ b/libs/rs/java/Samples/res/drawable/checker.png
Binary files differ
diff --git a/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java b/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
index d4e83d3..a15c4a1 100644
--- a/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
+++ b/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
@@ -19,11 +19,12 @@
 import java.io.Writer;
 
 import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.renderscript.*;
 import android.renderscript.ProgramStore.DepthFunc;
+import android.renderscript.Sampler.Value;
 import android.util.Log;
-import android.graphics.BitmapFactory;
-import android.graphics.Bitmap;
 
 
 public class RsRenderStatesRS {
@@ -42,7 +43,7 @@
         mOptionsARGB.inScaled = false;
         mOptionsARGB.inPreferredConfig = Bitmap.Config.ARGB_8888;
         mMode = 0;
-        mMaxModes = 8;
+        mMaxModes = 9;
         initRS();
     }
 
@@ -53,6 +54,8 @@
     private Sampler mLinearWrap;
     private Sampler mMipLinearWrap;
     private Sampler mNearestClamp;
+    private Sampler mMipLinearAniso8;
+    private Sampler mMipLinearAniso15;
 
     private ProgramStore mProgStoreBlendNoneDepth;
     private ProgramStore mProgStoreBlendNone;
@@ -74,10 +77,12 @@
 
     private ProgramRaster mCullBack;
     private ProgramRaster mCullFront;
+    private ProgramRaster mCullNone;
 
     private Allocation mTexTorus;
     private Allocation mTexOpaque;
     private Allocation mTexTransparent;
+    private Allocation mTexChecker;
 
     private Allocation mAllocPV;
 
@@ -243,10 +248,12 @@
         mTexTorus = loadTextureRGB(R.drawable.torusmap);
         mTexOpaque = loadTextureRGB(R.drawable.data);
         mTexTransparent = loadTextureARGB(R.drawable.leaf);
+        mTexChecker = loadTextureRGB(R.drawable.checker);
 
         mScript.set_gTexTorus(mTexTorus);
         mScript.set_gTexOpaque(mTexOpaque);
         mScript.set_gTexTransparent(mTexTransparent);
+        mScript.set_gTexChecker(mTexChecker);
     }
 
     private void initFonts() {
@@ -295,18 +302,32 @@
         mNearestClamp = Sampler.CLAMP_NEAREST(mRS);
         mMipLinearWrap = Sampler.WRAP_LINEAR_MIP_LINEAR(mRS);
 
+        bs = new Sampler.Builder(mRS);
+        bs.setMin(Sampler.Value.LINEAR_MIP_LINEAR);
+        bs.setMag(Sampler.Value.LINEAR);
+        bs.setWrapS(Sampler.Value.WRAP);
+        bs.setWrapT(Sampler.Value.WRAP);
+        bs.setAnisotropy(8.0f);
+        mMipLinearAniso8 = bs.create();
+        bs.setAnisotropy(15.0f);
+        mMipLinearAniso15 = bs.create();
+
         mScript.set_gLinearClamp(mLinearClamp);
         mScript.set_gLinearWrap(mLinearWrap);
         mScript.set_gMipLinearWrap(mMipLinearWrap);
+        mScript.set_gMipLinearAniso8(mMipLinearAniso8);
+        mScript.set_gMipLinearAniso15(mMipLinearAniso15);
         mScript.set_gNearestClamp(mNearestClamp);
     }
 
     private void initProgramRaster() {
         mCullBack = ProgramRaster.CULL_BACK(mRS);
         mCullFront = ProgramRaster.CULL_FRONT(mRS);
+        mCullNone = ProgramRaster.CULL_NONE(mRS);
 
         mScript.set_gCullBack(mCullBack);
         mScript.set_gCullFront(mCullFront);
+        mScript.set_gCullNone(mCullNone);
     }
 
     private void initRS() {
diff --git a/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs b/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
index 659e1e4..b471504 100644
--- a/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
+++ b/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
@@ -31,6 +31,7 @@
 rs_allocation gTexOpaque;
 rs_allocation gTexTorus;
 rs_allocation gTexTransparent;
+rs_allocation gTexChecker;
 
 rs_mesh gMbyNMesh;
 rs_mesh gTorusMesh;
@@ -47,10 +48,13 @@
 rs_sampler gLinearClamp;
 rs_sampler gLinearWrap;
 rs_sampler gMipLinearWrap;
+rs_sampler gMipLinearAniso8;
+rs_sampler gMipLinearAniso15;
 rs_sampler gNearestClamp;
 
 rs_program_raster gCullBack;
 rs_program_raster gCullFront;
+rs_program_raster gCullNone;
 
 // Custom vertex shader compunents
 VertexShaderConstants *gVSConstants;
@@ -64,11 +68,11 @@
 
 #pragma rs export_var(gProgVertex, gProgFragmentColor, gProgFragmentTexture)
 #pragma rs export_var(gProgStoreBlendNoneDepth, gProgStoreBlendNone, gProgStoreBlendAlpha, gProgStoreBlendAdd)
-#pragma rs export_var(gTexOpaque, gTexTorus, gTexTransparent)
+#pragma rs export_var(gTexOpaque, gTexTorus, gTexTransparent, gTexChecker)
 #pragma rs export_var(gMbyNMesh, gTorusMesh)
 #pragma rs export_var(gFontSans, gFontSerif, gFontSerifBold, gFontSerifItalic, gFontSerifBoldItalic, gFontMono)
-#pragma rs export_var(gLinearClamp, gLinearWrap, gMipLinearWrap, gNearestClamp)
-#pragma rs export_var(gCullBack, gCullFront)
+#pragma rs export_var(gLinearClamp, gLinearWrap, gMipLinearWrap, gMipLinearAniso8, gMipLinearAniso15, gNearestClamp)
+#pragma rs export_var(gCullBack, gCullFront, gCullNone)
 #pragma rs export_var(gVSConstants, gFSConstants, gVSInputs, gProgVertexCustom, gProgFragmentCustom, gProgFragmentMultitex)
 
 //What we are showing
@@ -110,7 +114,7 @@
     rsgBindProgramVertex(gProgVertex);
     // Setup the projectioni matrix
     rs_matrix4x4 proj;
-    rsMatrixLoadOrtho(&proj, 0, rsgGetWidth(), rsgGetHeight(), 0, -1,1);
+    rsMatrixLoadOrtho(&proj, 0, rsgGetWidth(), rsgGetHeight(), 0, -500, 500);
     rsgProgramVertexLoadProjectionMatrix(&proj);
 }
 
@@ -276,14 +280,12 @@
                          startX + width, startY + height, 0, 1.5, 1.5,
                          startX + width, startY, 0, 1.5, 0);
 
-
     rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
     rsgBindFont(gFontMono);
     rsgDrawText("Filtering: linear clamp", 10, 290);
     rsgDrawText("Filtering: linear wrap", 10, 590);
     rsgDrawText("Filtering: nearest clamp", 310, 290);
     rsgDrawText("Filtering: miplinear wrap", 310, 590);
-
 }
 
 float gTorusRotation = 0;
@@ -430,7 +432,7 @@
     rsgBindSampler(gProgFragmentMultitex, 0, gLinearClamp);
     rsgBindSampler(gProgFragmentMultitex, 1, gLinearWrap);
     rsgBindSampler(gProgFragmentMultitex, 2, gLinearClamp);
-    rsgBindTexture(gProgFragmentMultitex, 0, gTexOpaque);
+    rsgBindTexture(gProgFragmentMultitex, 0, gTexChecker);
     rsgBindTexture(gProgFragmentMultitex, 1, gTexTorus);
     rsgBindTexture(gProgFragmentMultitex, 2, gTexTransparent);
 
@@ -446,6 +448,69 @@
     rsgDrawText("Custom shader with multitexturing", 10, 280);
 }
 
+float gAnisoTime = 0.0f;
+uint anisoMode = 0;
+void displayAnisoSample() {
+
+    gAnisoTime += gDt;
+
+    rsgBindProgramVertex(gProgVertex);
+    float aspect = (float)rsgGetWidth() / (float)rsgGetHeight();
+    rs_matrix4x4 proj;
+    rsMatrixLoadPerspective(&proj, 30.0f, aspect, 0.1f, 100.0f);
+    rsgProgramVertexLoadProjectionMatrix(&proj);
+
+    rs_matrix4x4 matrix;
+    // Fragment shader with texture
+    rsgBindProgramStore(gProgStoreBlendNone);
+    rsgBindProgramFragment(gProgFragmentTexture);
+    rsMatrixLoadTranslate(&matrix, 0.0f, 0.0f, -10.0f);
+    rsMatrixRotate(&matrix, -80, 1.0f, 0.0f, 0.0f);
+    rsgProgramVertexLoadModelMatrix(&matrix);
+
+    rsgBindProgramRaster(gCullNone);
+
+    rsgBindTexture(gProgFragmentTexture, 0, gTexChecker);
+
+    if(gAnisoTime >= 5.0f) {
+        gAnisoTime = 0.0f;
+        anisoMode ++;
+        anisoMode = anisoMode % 3;
+    }
+
+    if(anisoMode == 0) {
+        rsgBindSampler(gProgFragmentTexture, 0, gMipLinearAniso8);
+    }
+    else if(anisoMode == 1) {
+        rsgBindSampler(gProgFragmentTexture, 0, gMipLinearAniso15);
+    }
+    else {
+        rsgBindSampler(gProgFragmentTexture, 0, gMipLinearWrap);
+    }
+
+    float startX = -15;
+    float startY = -15;
+    float width = 30;
+    float height = 30;
+    rsgDrawQuadTexCoords(startX, startY, 0, 0, 0,
+                         startX, startY + height, 0, 0, 10,
+                         startX + width, startY + height, 0, 10, 10,
+                         startX + width, startY, 0, 10, 0);
+
+    rsgBindProgramRaster(gCullBack);
+
+    rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
+    rsgBindFont(gFontMono);
+    if(anisoMode == 0) {
+        rsgDrawText("Anisotropic filtering 8", 10, 40);
+    }
+    else if(anisoMode == 1) {
+        rsgDrawText("Anisotropic filtering 15", 10, 40);
+    }
+    else {
+        rsgDrawText("Miplinear filtering", 10, 40);
+    }
+}
 
 int root(int launchID) {
 
@@ -479,6 +544,9 @@
     case 7:
         displayMultitextureSample();
         break;
+    case 8:
+        displayAnisoSample();
+        break;
     }
 
     return 10;
diff --git a/libs/rs/java/tests/src/com/android/rs/test/RSTestCore.java b/libs/rs/java/tests/src/com/android/rs/test/RSTestCore.java
index dbc9133..835dea2 100644
--- a/libs/rs/java/tests/src/com/android/rs/test/RSTestCore.java
+++ b/libs/rs/java/tests/src/com/android/rs/test/RSTestCore.java
@@ -21,6 +21,8 @@
 import android.util.Log;
 import java.util.ArrayList;
 import java.util.ListIterator;
+import java.util.Timer;
+import java.util.TimerTask;
 
 
 public class RSTestCore {
@@ -42,12 +44,18 @@
     private ArrayList<UnitTest> unitTests;
     private ListIterator<UnitTest> test_iter;
     private UnitTest activeTest;
+    private boolean stopTesting;
+
+    /* Periodic timer for ensuring future tests get scheduled */
+    private Timer mTimer;
+    public static final int RS_TIMER_PERIOD = 100;
 
     public void init(RenderScriptGL rs, Resources res, int width, int height) {
         mRS = rs;
         mRes = res;
         mWidth = width;
         mHeight = height;
+        stopTesting = false;
 
         mScript = new ScriptC_rslist(mRS, mRes, R.raw.rslist, true);
 
@@ -88,9 +96,17 @@
 
         test_iter = unitTests.listIterator();
         refreshTestResults(); /* Kick off the first test */
+
+        TimerTask pTask = new TimerTask() {
+            public void run() {
+                refreshTestResults();
+            }
+        };
+
+        mTimer = new Timer();
+        mTimer.schedule(pTask, RS_TIMER_PERIOD, RS_TIMER_PERIOD);
     }
 
-    static int count = 0;
     public void checkAndRunNextTest() {
         if (activeTest != null) {
             if (!activeTest.isAlive()) {
@@ -104,7 +120,7 @@
             }
         }
 
-        if (activeTest == null) {
+        if (!stopTesting && activeTest == null) {
             if (test_iter.hasNext()) {
                 activeTest = test_iter.next();
                 activeTest.start();
@@ -112,8 +128,14 @@
                  * should start running. The message handler in UnitTest.java
                  * ensures this. */
             }
+            else {
+                if (mTimer != null) {
+                    mTimer.cancel();
+                    mTimer.purge();
+                    mTimer = null;
+                }
+            }
         }
-        count++;
     }
 
     public void refreshTestResults() {
@@ -127,6 +149,29 @@
         }
     }
 
+    public void cleanup() {
+        stopTesting = true;
+        UnitTest t = activeTest;
+
+        /* Stop periodic refresh of testing */
+        if (mTimer != null) {
+            mTimer.cancel();
+            mTimer.purge();
+            mTimer = null;
+        }
+
+        /* Wait to exit until we finish the current test */
+        if (t != null) {
+            try {
+                t.join();
+            }
+            catch (InterruptedException e) {
+            }
+            t = null;
+        }
+
+    }
+
     public void newTouchPosition(float x, float y, float pressure, int id) {
     }
 
diff --git a/libs/rs/java/tests/src/com/android/rs/test/RSTestView.java b/libs/rs/java/tests/src/com/android/rs/test/RSTestView.java
index ce99c6d..b811d48 100644
--- a/libs/rs/java/tests/src/com/android/rs/test/RSTestView.java
+++ b/libs/rs/java/tests/src/com/android/rs/test/RSTestView.java
@@ -62,6 +62,7 @@
     @Override
     protected void onDetachedFromWindow() {
         if(mRS != null) {
+            mRender.cleanup();
             mRS = null;
             destroyRenderScript();
         }
diff --git a/libs/rs/java/tests/src/com/android/rs/test/UT_fp_mad.java b/libs/rs/java/tests/src/com/android/rs/test/UT_fp_mad.java
index 8391fb3..9d57e90 100644
--- a/libs/rs/java/tests/src/com/android/rs/test/UT_fp_mad.java
+++ b/libs/rs/java/tests/src/com/android/rs/test/UT_fp_mad.java
@@ -33,6 +33,7 @@
         pRS.mMessageCallback = mRsMessage;
         s.invoke_fp_mad_test(0, 0);
         pRS.finish();
+        waitForMessage();
         pRS.destroy();
     }
 }
diff --git a/libs/rs/java/tests/src/com/android/rs/test/UT_primitives.java b/libs/rs/java/tests/src/com/android/rs/test/UT_primitives.java
index bef6ec5..fb355dd 100644
--- a/libs/rs/java/tests/src/com/android/rs/test/UT_primitives.java
+++ b/libs/rs/java/tests/src/com/android/rs/test/UT_primitives.java
@@ -33,6 +33,7 @@
         pRS.mMessageCallback = mRsMessage;
         s.invoke_primitives_test(0, 0);
         pRS.finish();
+        waitForMessage();
         pRS.destroy();
     }
 }
diff --git a/libs/rs/java/tests/src/com/android/rs/test/UnitTest.java b/libs/rs/java/tests/src/com/android/rs/test/UnitTest.java
index 5eb0d67..c9d88a6 100644
--- a/libs/rs/java/tests/src/com/android/rs/test/UnitTest.java
+++ b/libs/rs/java/tests/src/com/android/rs/test/UnitTest.java
@@ -23,6 +23,7 @@
     public int result;
     private ScriptField_ListAllocs_s.Item mItem;
     private RSTestCore mRSTC;
+    private boolean msgHandled;
 
     /* These constants must match those in shared.rsh */
     public static final int RS_MSG_TEST_PASSED = 100;
@@ -35,6 +36,7 @@
         super();
         mRSTC = rstc;
         name = n;
+        msgHandled = false;
         result = initResult;
         testID = numTests++;
     }
@@ -67,6 +69,7 @@
 
             if (mItem != null) {
                 mItem.result = result;
+                msgHandled = true;
                 try {
                     mRSTC.refreshTestResults();
                 }
@@ -79,6 +82,12 @@
         }
     };
 
+    public void waitForMessage() {
+        while (!msgHandled) {
+            yield();
+        }
+    }
+
     public void setItem(ScriptField_ListAllocs_s.Item item) {
         mItem = item;
     }
diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec
index 2b7928f..a4752f4 100644
--- a/libs/rs/rs.spec
+++ b/libs/rs/rs.spec
@@ -288,6 +288,11 @@
 	param RsSamplerValue value
 	}
 
+SamplerSet2 {
+	param RsSamplerParam p
+	param float value
+	}
+
 SamplerCreate {
 	ret RsSampler
 	}
diff --git a/libs/rs/rsAdapter.cpp b/libs/rs/rsAdapter.cpp
index b4ec250..ef69b75 100644
--- a/libs/rs/rsAdapter.cpp
+++ b/libs/rs/rsAdapter.cpp
@@ -183,7 +183,6 @@
 
     uint32_t eSize = mAllocation.get()->getType()->getElementSizeBytes();
     uint32_t lineSize = eSize * w;
-    uint32_t destW = getDimX();
 
     const uint8_t *src = static_cast<const uint8_t *>(data);
     for (uint32_t line=yoff; line < (yoff+h); line++) {
diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp
index 87c4f2b..0356e4d 100644
--- a/libs/rs/rsAllocation.cpp
+++ b/libs/rs/rsAllocation.cpp
@@ -293,7 +293,6 @@
     }
 
     for (uint32_t line=yoff; line < (yoff+h); line++) {
-        uint8_t * ptr = static_cast<uint8_t *>(mPtr);
         if (mType->getElement()->getHasReferences()) {
             incRefs(src, w);
             decRefs(dst, w);
diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp
index b4d5014..4752b35 100644
--- a/libs/rs/rsContext.cpp
+++ b/libs/rs/rsContext.cpp
@@ -282,18 +282,19 @@
      rsc->props.mLogShadersUniforms = getProp("debug.rs.shader.uniforms");
      rsc->props.mLogVisual = getProp("debug.rs.visual");
 
-     ScriptTLSStruct *tlsStruct = new ScriptTLSStruct;
-     if (!tlsStruct) {
+     rsc->mTlsStruct = new ScriptTLSStruct;
+     if (!rsc->mTlsStruct) {
          LOGE("Error allocating tls storage");
          return NULL;
      }
-     tlsStruct->mContext = rsc;
-     tlsStruct->mScript = NULL;
-     int status = pthread_setspecific(rsc->gThreadTLSKey, tlsStruct);
+     rsc->mTlsStruct->mContext = rsc;
+     rsc->mTlsStruct->mScript = NULL;
+     int status = pthread_setspecific(rsc->gThreadTLSKey, rsc->mTlsStruct);
      if (status) {
          LOGE("pthread_setspecific %i", status);
      }
 
+     rsc->mScriptC.init(rsc);
      if (rsc->mIsGraphicsContext) {
          rsc->mStateRaster.init(rsc);
          rsc->setRaster(NULL);
@@ -360,6 +361,7 @@
          rsc->deinitEGL();
          pthread_mutex_unlock(&gInitMutex);
      }
+     delete rsc->mTlsStruct;
 
      LOGV("%p, RS Thread exited", rsc);
      return NULL;
@@ -386,6 +388,11 @@
 #endif
 
      setpriority(PRIO_PROCESS, rsc->mWorkers.mNativeThreadId[idx], rsc->mThreadPriority);
+     int status = pthread_setspecific(rsc->gThreadTLSKey, rsc->mTlsStruct);
+     if (status) {
+         LOGE("pthread_setspecific %i", status);
+     }
+
      while(rsc->mRunning) {
          rsc->mWorkers.mLaunchSignals[idx].wait();
          if (rsc->mWorkers.mLaunchCallback) {
diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h
index 2e84930..f90e4af 100644
--- a/libs/rs/rsContext.h
+++ b/libs/rs/rsContext.h
@@ -49,6 +49,24 @@
 
 namespace renderscript {
 
+#if 0
+#define CHECK_OBJ(o) { \
+    GET_TLS(); \
+    if(!ObjectBase::isValid(rsc, (const ObjectBase *)o)) {  \
+        LOGE("Bad object %p at %s, %i", o, __FILE__, __LINE__);  \
+    } \
+}
+#define CHECK_OBJ_OR_NULL(o) { \
+    GET_TLS(); \
+    if(o && !ObjectBase::isValid(rsc, (const ObjectBase *)o)) {  \
+        LOGE("Bad object %p at %s, %i", o, __FILE__, __LINE__);  \
+    } \
+}
+#else
+#define CHECK_OBJ(o)
+#define CHECK_OBJ_OR_NULL(o)
+#endif
+
 class Context
 {
 public:
@@ -64,6 +82,7 @@
         Context * mContext;
         Script * mScript;
     };
+    ScriptTLSStruct *mTlsStruct;
 
     typedef void (*WorkerCallback_t)(void *usr, uint32_t idx);
 
diff --git a/libs/rs/rsContextHostStub.h b/libs/rs/rsContextHostStub.h
index f30915e..06298e8 100644
--- a/libs/rs/rsContextHostStub.h
+++ b/libs/rs/rsContextHostStub.h
@@ -111,6 +111,9 @@
         bool mLogScripts;
         bool mLogObjects;
         bool mLogShaders;
+        bool mLogShadersAttr;
+        bool mLogShadersUniforms;
+        bool mLogVisual;
     } props;
 
     void dumpDebug() const {    }
@@ -120,6 +123,7 @@
     mutable const ObjectBase * mObjHead;
 
     bool ext_OES_texture_npot() const {return mGL.OES_texture_npot;}
+    float ext_texture_max_aniso() const {return 1.0f;}
     uint32_t getMaxFragmentTextures() const {return mGL.mMaxFragmentTextureImageUnits;}
     uint32_t getMaxFragmentUniformVectors() const {return mGL.mMaxFragmentUniformVectors;}
     uint32_t getMaxVertexUniformVectors() const {return mGL.mMaxVertexUniformVectors;}
diff --git a/libs/rs/rsFont.cpp b/libs/rs/rsFont.cpp
index 4f8d8df..12dedac 100644
--- a/libs/rs/rsFont.cpp
+++ b/libs/rs/rsFont.cpp
@@ -743,6 +743,8 @@
 {
     mInitialized = false;
 
+    mFontShaderFConstant.clear();
+
     mIndexBuffer.clear();
     mVertexArray.clear();
 
diff --git a/libs/rs/rsObjectBase.cpp b/libs/rs/rsObjectBase.cpp
index 48f1fee..f69cb15 100644
--- a/libs/rs/rsObjectBase.cpp
+++ b/libs/rs/rsObjectBase.cpp
@@ -61,6 +61,7 @@
     if (mRSC) {
         remove();
     }
+    rsAssert(rsc);
     mRSC = rsc;
     if (rsc) {
         add();
@@ -194,3 +195,15 @@
     }
 }
 
+bool ObjectBase::isValid(const Context *rsc, const ObjectBase *obj)
+{
+    const ObjectBase * o = rsc->mObjHead;
+    while (o) {
+        if (o == obj) {
+            return true;
+        }
+        o = o->mNext;
+    }
+    return false;
+}
+
diff --git a/libs/rs/rsObjectBase.h b/libs/rs/rsObjectBase.h
index ad95b81..59fb4a6 100644
--- a/libs/rs/rsObjectBase.h
+++ b/libs/rs/rsObjectBase.h
@@ -56,6 +56,8 @@
     virtual void serialize(OStream *stream) const = 0;
     virtual RsA3DClassID getClassId() const = 0;
 
+    static bool isValid(const Context *rsc, const ObjectBase *obj);
+
 protected:
     const char *mAllocFile;
     uint32_t mAllocLine;
diff --git a/libs/rs/rsSampler.cpp b/libs/rs/rsSampler.cpp
index c6a848c..180d78e 100644
--- a/libs/rs/rsSampler.cpp
+++ b/libs/rs/rsSampler.cpp
@@ -44,7 +44,8 @@
                  RsSamplerValue minFilter,
                  RsSamplerValue wrapS,
                  RsSamplerValue wrapT,
-                 RsSamplerValue wrapR) : ObjectBase(rsc)
+                 RsSamplerValue wrapR,
+                 float aniso) : ObjectBase(rsc)
 {
     mAllocFile = __FILE__;
     mAllocLine = __LINE__;
@@ -53,6 +54,7 @@
     mWrapS = wrapS;
     mWrapT = wrapT;
     mWrapR = wrapR;
+    mAniso = aniso;
 }
 
 Sampler::~Sampler()
@@ -93,6 +95,11 @@
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, trans[mWrapT]);
     }
 
+    float anisoValue = rsMin(rsc->ext_texture_max_aniso(), mAniso);
+    if(rsc->ext_texture_max_aniso() > 1.0f) {
+        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisoValue);
+    }
+
     rsc->checkError("Sampler::setupGL2 tex env");
 }
 
@@ -147,6 +154,7 @@
     ss->mWrapS = RS_SAMPLER_WRAP;
     ss->mWrapT = RS_SAMPLER_WRAP;
     ss->mWrapR = RS_SAMPLER_WRAP;
+    ss->mAniso = 1.0f;
 }
 
 void rsi_SamplerSet(Context *rsc, RsSamplerParam param, RsSamplerValue value)
@@ -169,21 +177,37 @@
     case RS_SAMPLER_WRAP_R:
         ss->mWrapR = value;
         break;
+    default:
+        LOGE("Attempting to set invalid value on sampler");
+        break;
     }
+}
 
+void rsi_SamplerSet2(Context *rsc, RsSamplerParam param, float value)
+{
+    SamplerState * ss = &rsc->mStateSampler;
+
+    switch(param) {
+    case RS_SAMPLER_ANISO:
+        ss->mAniso = value;
+        break;
+    default:
+        LOGE("Attempting to set invalid value on sampler");
+        break;
+    }
 }
 
 RsSampler rsi_SamplerCreate(Context *rsc)
 {
     SamplerState * ss = &rsc->mStateSampler;
 
-
     Sampler * s = new Sampler(rsc,
                               ss->mMagFilter,
                               ss->mMinFilter,
                               ss->mWrapS,
                               ss->mWrapT,
-                              ss->mWrapR);
+                              ss->mWrapR,
+                              ss->mAniso);
     s->incUserRef();
     return s;
 }
diff --git a/libs/rs/rsSampler.h b/libs/rs/rsSampler.h
index 32a8efd..4946355 100644
--- a/libs/rs/rsSampler.h
+++ b/libs/rs/rsSampler.h
@@ -36,7 +36,8 @@
             RsSamplerValue minFilter,
             RsSamplerValue wrapS,
             RsSamplerValue wrapT,
-            RsSamplerValue wrapR);
+            RsSamplerValue wrapR,
+            float aniso = 1.0f);
 
     virtual ~Sampler();
 
@@ -56,6 +57,7 @@
     RsSamplerValue mWrapS;
     RsSamplerValue mWrapT;
     RsSamplerValue mWrapR;
+    float mAniso;
 
     int32_t mBoundSlot;
 
@@ -74,6 +76,7 @@
     RsSamplerValue mWrapS;
     RsSamplerValue mWrapT;
     RsSamplerValue mWrapR;
+    float mAniso;
 
 
     ObjectBaseRef<Sampler> mSamplers[RS_MAX_SAMPLER_SLOT];
diff --git a/libs/rs/rsScriptC.cpp b/libs/rs/rsScriptC.cpp
index e9621b9..c6418be 100644
--- a/libs/rs/rsScriptC.cpp
+++ b/libs/rs/rsScriptC.cpp
@@ -349,25 +349,29 @@
 
 ScriptCState::ScriptCState()
 {
-    mScript = NULL;
-    clear();
+    mScript.clear();
 }
 
 ScriptCState::~ScriptCState()
 {
-    delete mScript;
-    mScript = NULL;
+    mScript.clear();
 }
 
-void ScriptCState::clear()
+void ScriptCState::init(Context *rsc)
 {
+    clear(rsc);
+}
+
+void ScriptCState::clear(Context *rsc)
+{
+    rsAssert(rsc);
     for (uint32_t ct=0; ct < MAX_SCRIPT_BANKS; ct++) {
         mConstantBufferTypes[ct].clear();
         mSlotWritable[ct] = false;
     }
 
-    delete mScript;
-    mScript = new ScriptC(NULL);
+    mScript.clear();
+    mScript.set(new ScriptC(rsc));
 }
 
 static BCCvoid* symbolLookup(BCCvoid* pContext, const BCCchar* name)
@@ -503,7 +507,7 @@
 void rsi_ScriptCBegin(Context * rsc)
 {
     ScriptCState *ss = &rsc->mScriptC;
-    ss->clear();
+    ss->clear(rsc);
 }
 
 void rsi_ScriptCSetText(Context *rsc, const char *text, uint32_t len)
@@ -522,10 +526,10 @@
 {
     ScriptCState *ss = &rsc->mScriptC;
 
-    ScriptC *s = ss->mScript;
-    ss->mScript = NULL;
+    ObjectBaseRef<ScriptC> s = ss->mScript.get();
+    ss->mScript.clear();
 
-    ss->runCompiler(rsc, s);
+    ss->runCompiler(rsc, s.get());
     s->incUserRef();
     s->setContext(rsc);
     for (int ct=0; ct < MAX_SCRIPT_BANKS; ct++) {
@@ -533,8 +537,8 @@
         s->mSlotWritable[ct] = ss->mSlotWritable[ct];
     }
 
-    ss->clear();
-    return s;
+    ss->clear(rsc);
+    return s.get();
 }
 
 }
diff --git a/libs/rs/rsScriptC.h b/libs/rs/rsScriptC.h
index 9d09b0b..7ec80aa 100644
--- a/libs/rs/rsScriptC.h
+++ b/libs/rs/rsScriptC.h
@@ -79,14 +79,16 @@
     ScriptCState();
     ~ScriptCState();
 
-    ScriptC *mScript;
+    ObjectBaseRef<ScriptC> mScript;
 
     ObjectBaseRef<const Type> mConstantBufferTypes[MAX_SCRIPT_BANKS];
     //String8 mSlotNames[MAX_SCRIPT_BANKS];
     bool mSlotWritable[MAX_SCRIPT_BANKS];
     //String8 mInvokableNames[MAX_SCRIPT_BANKS];
 
-    void clear();
+    void init(Context *rsc);
+
+    void clear(Context *rsc);
     void runCompiler(Context *rsc, ScriptC *s);
 
     struct SymbolTable_t {
diff --git a/libs/rs/rsScriptC_Lib.cpp b/libs/rs/rsScriptC_Lib.cpp
index 22fd421..e0de867 100644
--- a/libs/rs/rsScriptC_Lib.cpp
+++ b/libs/rs/rsScriptC_Lib.cpp
@@ -203,45 +203,46 @@
 
 static uint32_t SC_allocGetDimX(RsAllocation va)
 {
-    GET_TLS();
     const Allocation *a = static_cast<const Allocation *>(va);
-    //LOGE("SC_allocGetDimX a=%p", a);
-    //LOGE(" type=%p", a->getType());
+    CHECK_OBJ(a);
+    //LOGE("SC_allocGetDimX a=%p  type=%p", a, a->getType());
     return a->getType()->getDimX();
 }
 
 static uint32_t SC_allocGetDimY(RsAllocation va)
 {
-    GET_TLS();
     const Allocation *a = static_cast<const Allocation *>(va);
+    CHECK_OBJ(a);
     return a->getType()->getDimY();
 }
 
 static uint32_t SC_allocGetDimZ(RsAllocation va)
 {
-    GET_TLS();
     const Allocation *a = static_cast<const Allocation *>(va);
+    CHECK_OBJ(a);
     return a->getType()->getDimZ();
 }
 
 static uint32_t SC_allocGetDimLOD(RsAllocation va)
 {
-    GET_TLS();
     const Allocation *a = static_cast<const Allocation *>(va);
+    CHECK_OBJ(a);
     return a->getType()->getDimLOD();
 }
 
 static uint32_t SC_allocGetDimFaces(RsAllocation va)
 {
-    GET_TLS();
     const Allocation *a = static_cast<const Allocation *>(va);
+    CHECK_OBJ(a);
     return a->getType()->getDimFaces();
 }
 
 static const void * SC_getElementAtX(RsAllocation va, uint32_t x)
 {
     const Allocation *a = static_cast<const Allocation *>(va);
+    CHECK_OBJ(a);
     const Type *t = a->getType();
+    CHECK_OBJ(t);
     const uint8_t *p = (const uint8_t *)a->getPtr();
     return &p[t->getElementSizeBytes() * x];
 }
@@ -249,7 +250,9 @@
 static const void * SC_getElementAtXY(RsAllocation va, uint32_t x, uint32_t y)
 {
     const Allocation *a = static_cast<const Allocation *>(va);
+    CHECK_OBJ(a);
     const Type *t = a->getType();
+    CHECK_OBJ(t);
     const uint8_t *p = (const uint8_t *)a->getPtr();
     return &p[t->getElementSizeBytes() * (x + y*t->getDimX())];
 }
@@ -257,7 +260,9 @@
 static const void * SC_getElementAtXYZ(RsAllocation va, uint32_t x, uint32_t y, uint32_t z)
 {
     const Allocation *a = static_cast<const Allocation *>(va);
+    CHECK_OBJ(a);
     const Type *t = a->getType();
+    CHECK_OBJ(t);
     const uint8_t *p = (const uint8_t *)a->getPtr();
     return &p[t->getElementSizeBytes() * (x + y*t->getDimX())];
 }
@@ -265,9 +270,11 @@
 static void SC_setObject(void **vdst, void * vsrc) {
     //LOGE("SC_setObject  %p,%p  %p", vdst, *vdst, vsrc);
     if (vsrc) {
+        CHECK_OBJ(vsrc);
         static_cast<ObjectBase *>(vsrc)->incSysRef();
     }
     if (vdst[0]) {
+        CHECK_OBJ(vdst[0]);
         static_cast<ObjectBase *>(vdst[0])->decSysRef();
     }
     *vdst = vsrc;
@@ -276,6 +283,7 @@
 static void SC_clearObject(void **vdst) {
     //LOGE("SC_clearObject  %p,%p", vdst, *vdst);
     if (vdst[0]) {
+        CHECK_OBJ(vdst[0]);
         static_cast<ObjectBase *>(vdst[0])->decSysRef();
     }
     *vdst = NULL;
diff --git a/libs/rs/rsScriptC_LibGL.cpp b/libs/rs/rsScriptC_LibGL.cpp
index fd4c379..7fd6406 100644
--- a/libs/rs/rsScriptC_LibGL.cpp
+++ b/libs/rs/rsScriptC_LibGL.cpp
@@ -44,6 +44,8 @@
 
 static void SC_bindTexture(RsProgramFragment vpf, uint32_t slot, RsAllocation va)
 {
+    CHECK_OBJ_OR_NULL(va);
+    CHECK_OBJ(vpf);
     GET_TLS();
     rsi_ProgramBindTexture(rsc,
                            static_cast<ProgramFragment *>(vpf),
@@ -54,6 +56,8 @@
 
 static void SC_bindSampler(RsProgramFragment vpf, uint32_t slot, RsSampler vs)
 {
+    CHECK_OBJ_OR_NULL(vs);
+    CHECK_OBJ(vpf);
     GET_TLS();
     rsi_ProgramBindSampler(rsc,
                            static_cast<ProgramFragment *>(vpf),
@@ -64,24 +68,28 @@
 
 static void SC_bindProgramStore(RsProgramStore pfs)
 {
+    CHECK_OBJ_OR_NULL(pfs);
     GET_TLS();
     rsi_ContextBindProgramStore(rsc, pfs);
 }
 
 static void SC_bindProgramFragment(RsProgramFragment pf)
 {
+    CHECK_OBJ_OR_NULL(pf);
     GET_TLS();
     rsi_ContextBindProgramFragment(rsc, pf);
 }
 
 static void SC_bindProgramVertex(RsProgramVertex pv)
 {
+    CHECK_OBJ_OR_NULL(pv);
     GET_TLS();
     rsi_ContextBindProgramVertex(rsc, pv);
 }
 
 static void SC_bindProgramRaster(RsProgramRaster pv)
 {
+    CHECK_OBJ_OR_NULL(pv);
     GET_TLS();
     rsi_ContextBindProgramRaster(rsc, pv);
 }
@@ -112,6 +120,7 @@
 static void SC_pfConstantColor(RsProgramFragment vpf, float r, float g, float b, float a)
 {
     GET_TLS();
+    CHECK_OBJ(vpf);
     ProgramFragment *pf = static_cast<ProgramFragment *>(vpf);
     pf->setConstantColor(rsc, r, g, b, a);
 }
@@ -228,6 +237,7 @@
 
 static void SC_drawMesh(RsMesh vsm)
 {
+    CHECK_OBJ(vsm);
     GET_TLS();
     Mesh *sm = static_cast<Mesh *>(vsm);
     if (!rsc->setupCheck()) {
@@ -238,6 +248,7 @@
 
 static void SC_drawMeshPrimitive(RsMesh vsm, uint32_t primIndex)
 {
+    CHECK_OBJ(vsm);
     GET_TLS();
     Mesh *sm = static_cast<Mesh *>(vsm);
     if (!rsc->setupCheck()) {
@@ -248,6 +259,7 @@
 
 static void SC_drawMeshPrimitiveRange(RsMesh vsm, uint32_t primIndex, uint32_t start, uint32_t len)
 {
+    CHECK_OBJ(vsm);
     GET_TLS();
     Mesh *sm = static_cast<Mesh *>(vsm);
     if (!rsc->setupCheck()) {
@@ -259,6 +271,7 @@
 static void SC_meshComputeBoundingBox(RsMesh vsm, float *minX, float *minY, float *minZ,
                                                      float *maxX, float *maxY, float *maxZ)
 {
+    CHECK_OBJ(vsm);
     GET_TLS();
     Mesh *sm = static_cast<Mesh *>(vsm);
     sm->computeBBox();
@@ -285,17 +298,20 @@
 
 static void SC_uploadToTexture2(RsAllocation va, uint32_t baseMipLevel)
 {
+    CHECK_OBJ(va);
     GET_TLS();
     rsi_AllocationUploadToTexture(rsc, va, false, baseMipLevel);
 }
 static void SC_uploadToTexture(RsAllocation va)
 {
+    CHECK_OBJ(va);
     GET_TLS();
     rsi_AllocationUploadToTexture(rsc, va, false, 0);
 }
 
 static void SC_uploadToBufferObject(RsAllocation va)
 {
+    CHECK_OBJ(va);
     GET_TLS();
     rsi_AllocationUploadToBufferObject(rsc, va);
 }
@@ -336,6 +352,7 @@
 
 static void SC_DrawTextAlloc(RsAllocation va, int x, int y)
 {
+    CHECK_OBJ(va);
     GET_TLS();
     Allocation *alloc = static_cast<Allocation *>(va);
     rsc->mStateFont.renderText(alloc, x, y);
@@ -349,6 +366,7 @@
 
 static void SC_BindFont(RsFont font)
 {
+    CHECK_OBJ(font);
     GET_TLS();
     rsi_ContextBindFont(rsc, font);
 }
diff --git a/libs/rs/rsType.cpp b/libs/rs/rsType.cpp
index 1ee07cd..fc037a3 100644
--- a/libs/rs/rsType.cpp
+++ b/libs/rs/rsType.cpp
@@ -321,8 +321,6 @@
         break;
     }
 
-
-    int32_t arrayNum = dim - RS_DIMENSION_ARRAY_0;
     if ((dim < 0) || (dim > RS_DIMENSION_MAX)) {
         LOGE("rsTypeAdd: Bad dimension");
         //error
diff --git a/libs/surfaceflinger_client/ISurfaceComposer.cpp b/libs/surfaceflinger_client/ISurfaceComposer.cpp
index 040060e..d676f5e 100644
--- a/libs/surfaceflinger_client/ISurfaceComposer.cpp
+++ b/libs/surfaceflinger_client/ISurfaceComposer.cpp
@@ -126,11 +126,14 @@
 
     virtual status_t captureScreen(DisplayID dpy,
             sp<IMemoryHeap>* heap,
-            uint32_t* width, uint32_t* height, PixelFormat* format)
+            uint32_t* width, uint32_t* height, PixelFormat* format,
+            uint32_t reqWidth, uint32_t reqHeight)
     {
         Parcel data, reply;
         data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
         data.writeInt32(dpy);
+        data.writeInt32(reqWidth);
+        data.writeInt32(reqHeight);
         remote()->transact(BnSurfaceComposer::CAPTURE_SCREEN, data, &reply);
         *heap = interface_cast<IMemoryHeap>(reply.readStrongBinder());
         *width = reply.readInt32();
@@ -208,10 +211,13 @@
         case CAPTURE_SCREEN: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             DisplayID dpy = data.readInt32();
+            uint32_t reqWidth = data.readInt32();
+            uint32_t reqHeight = data.readInt32();
             sp<IMemoryHeap> heap;
             uint32_t w, h;
             PixelFormat f;
-            status_t res = captureScreen(dpy, &heap, &w, &h, &f);
+            status_t res = captureScreen(dpy, &heap, &w, &h, &f,
+                    reqWidth, reqHeight);
             reply->writeStrongBinder(heap->asBinder());
             reply->writeInt32(w);
             reply->writeInt32(h);
diff --git a/libs/surfaceflinger_client/SurfaceComposerClient.cpp b/libs/surfaceflinger_client/SurfaceComposerClient.cpp
index 4096ac6..f270461 100644
--- a/libs/surfaceflinger_client/SurfaceComposerClient.cpp
+++ b/libs/surfaceflinger_client/SurfaceComposerClient.cpp
@@ -545,5 +545,55 @@
 }
 
 // ----------------------------------------------------------------------------
+
+ScreenshotClient::ScreenshotClient()
+    : mWidth(0), mHeight(0), mFormat(PIXEL_FORMAT_NONE) {
+}
+
+status_t ScreenshotClient::update() {
+    sp<ISurfaceComposer> s(ComposerService::getComposerService());
+    if (s == NULL) return NO_INIT;
+    mHeap = 0;
+    return s->captureScreen(0, &mHeap,
+            &mWidth, &mHeight, &mFormat, 0, 0);
+}
+
+status_t ScreenshotClient::update(uint32_t reqWidth, uint32_t reqHeight) {
+    sp<ISurfaceComposer> s(ComposerService::getComposerService());
+    if (s == NULL) return NO_INIT;
+    mHeap = 0;
+    return s->captureScreen(0, &mHeap,
+            &mWidth, &mHeight, &mFormat, reqWidth, reqHeight);
+}
+
+void ScreenshotClient::release() {
+    mHeap = 0;
+}
+
+void const* ScreenshotClient::getPixels() const {
+    return mHeap->getBase();
+}
+
+uint32_t ScreenshotClient::getWidth() const {
+    return mWidth;
+}
+
+uint32_t ScreenshotClient::getHeight() const {
+    return mHeight;
+}
+
+PixelFormat ScreenshotClient::getFormat() const {
+    return mFormat;
+}
+
+uint32_t ScreenshotClient::getStride() const {
+    return mWidth;
+}
+
+size_t ScreenshotClient::getSize() const {
+    return mHeap->getSize();
+}
+
+// ----------------------------------------------------------------------------
 }; // namespace android
 
diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp
index f2b029a..1d35e10 100644
--- a/libs/ui/InputReader.cpp
+++ b/libs/ui/InputReader.cpp
@@ -33,6 +33,9 @@
 #include <math.h>
 
 #define INDENT "  "
+#define INDENT2 "    "
+#define INDENT3 "      "
+#define INDENT4 "        "
 
 namespace android {
 
@@ -63,6 +66,10 @@
     return sqrtf(x * x + y * y);
 }
 
+static inline const char* toString(bool value) {
+    return value ? "true" : "false";
+}
+
 
 int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState) {
     int32_t mask;
@@ -236,16 +243,14 @@
     String8 name = mEventHub->getDeviceName(deviceId);
     uint32_t classes = mEventHub->getDeviceClasses(deviceId);
 
-    // Write a log message about the added device as a heading for subsequent log messages.
-    LOGI("Device added: id=0x%x, name=%s", deviceId, name.string());
-
     InputDevice* device = createDevice(deviceId, name, classes);
     device->configure();
 
     if (device->isIgnored()) {
-        LOGI(INDENT "Sources: none (device is ignored)");
+        LOGI("Device added: id=0x%x, name=%s (ignored non-input device)", deviceId, name.string());
     } else {
-        LOGI(INDENT "Sources: 0x%08x", device->getSources());
+        LOGI("Device added: id=0x%x, name=%s, sources=%08x", deviceId, name.string(),
+                device->getSources());
     }
 
     bool added = false;
@@ -287,7 +292,6 @@
         return;
     }
 
-    // Write a log message about the removed device as a heading for subsequent log messages.
     if (device->isIgnored()) {
         LOGI("Device removed: id=0x%x, name=%s (ignored non-input device)",
                 device->getId(), device->getName().string());
@@ -571,59 +575,15 @@
 }
 
 void InputReader::dump(String8& dump) {
-    dumpDeviceInfo(dump);
-}
+    { // acquire device registry reader lock
+        RWLock::AutoRLock _rl(mDeviceRegistryLock);
 
-static void dumpMotionRange(String8& dump,
-        const char* name, const InputDeviceInfo::MotionRange* range) {
-    if (range) {
-        dump.appendFormat("      %s = { min: %0.3f, max: %0.3f, flat: %0.3f, fuzz: %0.3f }\n",
-                name, range->min, range->max, range->flat, range->fuzz);
-    }
-}
-
-#define DUMP_MOTION_RANGE(range) \
-    dumpMotionRange(dump, #range, deviceInfo.getMotionRange(AINPUT_MOTION_RANGE_##range));
-
-void InputReader::dumpDeviceInfo(String8& dump) {
-    Vector<int32_t> deviceIds;
-    getInputDeviceIds(deviceIds);
-
-    InputDeviceInfo deviceInfo;
-    for (size_t i = 0; i < deviceIds.size(); i++) {
-        int32_t deviceId = deviceIds[i];
-
-        status_t result = getInputDeviceInfo(deviceId, & deviceInfo);
-        if (result == NAME_NOT_FOUND) {
-            continue;
-        } else if (result != OK) {
-            dump.appendFormat("  ** Unexpected error %d getting information about input devices.\n",
-                    result);
-            continue;
+        for (size_t i = 0; i < mDevices.size(); i++) {
+            mDevices.valueAt(i)->dump(dump);
         }
-
-        dump.appendFormat("  Device %d: '%s'\n",
-                deviceInfo.getId(), deviceInfo.getName().string());
-        dump.appendFormat("    sources = 0x%08x\n",
-                deviceInfo.getSources());
-        dump.appendFormat("    keyboardType = %d\n",
-                deviceInfo.getKeyboardType());
-
-        dump.append("    motion ranges:\n");
-        DUMP_MOTION_RANGE(X);
-        DUMP_MOTION_RANGE(Y);
-        DUMP_MOTION_RANGE(PRESSURE);
-        DUMP_MOTION_RANGE(SIZE);
-        DUMP_MOTION_RANGE(TOUCH_MAJOR);
-        DUMP_MOTION_RANGE(TOUCH_MINOR);
-        DUMP_MOTION_RANGE(TOOL_MAJOR);
-        DUMP_MOTION_RANGE(TOOL_MINOR);
-        DUMP_MOTION_RANGE(ORIENTATION);
-    }
+    } // release device registy reader lock
 }
 
-#undef DUMP_MOTION_RANGE
-
 
 // --- InputReaderThread ---
 
@@ -654,6 +614,43 @@
     mMappers.clear();
 }
 
+static void dumpMotionRange(String8& dump, const InputDeviceInfo& deviceInfo,
+        int32_t rangeType, const char* name) {
+    const InputDeviceInfo::MotionRange* range = deviceInfo.getMotionRange(rangeType);
+    if (range) {
+        dump.appendFormat(INDENT3 "%s: min=%0.3f, max=%0.3f, flat=%0.3f, fuzz=%0.3f\n",
+                name, range->min, range->max, range->flat, range->fuzz);
+    }
+}
+
+void InputDevice::dump(String8& dump) {
+    InputDeviceInfo deviceInfo;
+    getDeviceInfo(& deviceInfo);
+
+    dump.appendFormat(INDENT "Device 0x%x: %s\n", deviceInfo.getId(),
+            deviceInfo.getName().string());
+    dump.appendFormat(INDENT2 "Sources: 0x%08x\n", deviceInfo.getSources());
+    dump.appendFormat(INDENT2 "KeyboardType: %d\n", deviceInfo.getKeyboardType());
+    if (!deviceInfo.getMotionRanges().isEmpty()) {
+        dump.append(INDENT2 "Motion Ranges:\n");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_X, "X");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_Y, "Y");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_PRESSURE, "Pressure");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_SIZE, "Size");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOUCH_MAJOR, "TouchMajor");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOUCH_MINOR, "TouchMinor");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOOL_MAJOR, "ToolMajor");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOOL_MINOR, "ToolMinor");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_ORIENTATION, "Orientation");
+    }
+
+    size_t numMappers = mMappers.size();
+    for (size_t i = 0; i < numMappers; i++) {
+        InputMapper* mapper = mMappers[i];
+        mapper->dump(dump);
+    }
+}
+
 void InputDevice::addMapper(InputMapper* mapper) {
     mMappers.add(mapper);
 }
@@ -763,6 +760,9 @@
     info->addSource(getSources());
 }
 
+void InputMapper::dump(String8& dump) {
+}
+
 void InputMapper::configure() {
 }
 
@@ -856,6 +856,19 @@
     info->setKeyboardType(mKeyboardType);
 }
 
+void KeyboardInputMapper::dump(String8& dump) {
+    { // acquire lock
+        AutoMutex _l(mLock);
+        dump.append(INDENT2 "Keyboard Input Mapper:\n");
+        dump.appendFormat(INDENT3 "AssociatedDisplayId: %d\n", mAssociatedDisplayId);
+        dump.appendFormat(INDENT3 "Sources: 0x%x\n", mSources);
+        dump.appendFormat(INDENT3 "KeyboardType: %d\n", mKeyboardType);
+        dump.appendFormat(INDENT3 "KeyDowns: %d keys currently down\n", mLocked.keyDowns.size());
+        dump.appendFormat(INDENT3 "MetaState: 0x%0x\n", mLocked.metaState);
+        dump.appendFormat(INDENT3 "DownTime: %lld\n", mLocked.downTime);
+    } // release lock
+}
+
 void KeyboardInputMapper::reset() {
     for (;;) {
         int32_t keyCode, scanCode;
@@ -1044,6 +1057,18 @@
     info->addMotionRange(AINPUT_MOTION_RANGE_Y, -1.0f, 1.0f, 0.0f, mYScale);
 }
 
+void TrackballInputMapper::dump(String8& dump) {
+    { // acquire lock
+        AutoMutex _l(mLock);
+        dump.append(INDENT2 "Trackball Input Mapper:\n");
+        dump.appendFormat(INDENT3 "AssociatedDisplayId: %d\n", mAssociatedDisplayId);
+        dump.appendFormat(INDENT3 "XPrecision: %0.3f\n", mXPrecision);
+        dump.appendFormat(INDENT3 "YPrecision: %0.3f\n", mYPrecision);
+        dump.appendFormat(INDENT3 "Down: %s\n", toString(mLocked.down));
+        dump.appendFormat(INDENT3 "DownTime: %lld\n", mLocked.downTime);
+    } // release lock
+}
+
 void TrackballInputMapper::initializeLocked() {
     mAccumulator.clear();
 
@@ -1275,6 +1300,21 @@
     } // release lock
 }
 
+void TouchInputMapper::dump(String8& dump) {
+    { // acquire lock
+        AutoMutex _l(mLock);
+        dump.append(INDENT2 "Touch Input Mapper:\n");
+        dump.appendFormat(INDENT3 "AssociatedDisplayId: %d\n", mAssociatedDisplayId);
+        dumpParameters(dump);
+        dumpVirtualKeysLocked(dump);
+        dumpRawAxes(dump);
+        dumpCalibration(dump);
+        dumpSurfaceLocked(dump);
+        dump.appendFormat(INDENT3 "XPrecision: %0.3f\n", mLocked.xPrecision);
+        dump.appendFormat(INDENT3 "YPrecision: %0.3f\n", mLocked.yPrecision);
+    } // release lock
+}
+
 void TouchInputMapper::initializeLocked() {
     mCurrentTouch.clear();
     mLastTouch.clear();
@@ -1301,16 +1341,13 @@
 
     // Configure basic parameters.
     configureParameters();
-    logParameters();
 
     // Configure absolute axis information.
     configureRawAxes();
-    logRawAxes();
 
     // Prepare input device calibration.
     parseCalibration();
     resolveCalibration();
-    logCalibration();
 
     { // acquire lock
         AutoMutex _l(mLock);
@@ -1326,16 +1363,13 @@
     mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents();
 }
 
-void TouchInputMapper::logParameters() {
-    if (mParameters.useBadTouchFilter) {
-        LOGI(INDENT "Bad touch filter enabled.");
-    }
-    if (mParameters.useAveragingTouchFilter) {
-        LOGI(INDENT "Averaging touch filter enabled.");
-    }
-    if (mParameters.useJumpyTouchFilter) {
-        LOGI(INDENT "Jumpy touch filter enabled.");
-    }
+void TouchInputMapper::dumpParameters(String8& dump) {
+    dump.appendFormat(INDENT3 "UseBadTouchFilter: %s\n",
+            toString(mParameters.useBadTouchFilter));
+    dump.appendFormat(INDENT3 "UseAveragingTouchFilter: %s\n",
+            toString(mParameters.useAveragingTouchFilter));
+    dump.appendFormat(INDENT3 "UseJumpyTouchFilter: %s\n",
+            toString(mParameters.useJumpyTouchFilter));
 }
 
 void TouchInputMapper::configureRawAxes() {
@@ -1349,24 +1383,25 @@
     mRawAxes.orientation.clear();
 }
 
-static void logAxisInfo(RawAbsoluteAxisInfo axis, const char* name) {
+static void dumpAxisInfo(String8& dump, RawAbsoluteAxisInfo axis, const char* name) {
     if (axis.valid) {
-        LOGI(INDENT "Raw %s axis: min=%d, max=%d, flat=%d, fuzz=%d",
+        dump.appendFormat(INDENT4 "%s: min=%d, max=%d, flat=%d, fuzz=%d\n",
                 name, axis.minValue, axis.maxValue, axis.flat, axis.fuzz);
     } else {
-        LOGI(INDENT "Raw %s axis: unknown range", name);
+        dump.appendFormat(INDENT4 "%s: unknown range\n", name);
     }
 }
 
-void TouchInputMapper::logRawAxes() {
-    logAxisInfo(mRawAxes.x, "x");
-    logAxisInfo(mRawAxes.y, "y");
-    logAxisInfo(mRawAxes.pressure, "pressure");
-    logAxisInfo(mRawAxes.touchMajor, "touchMajor");
-    logAxisInfo(mRawAxes.touchMinor, "touchMinor");
-    logAxisInfo(mRawAxes.toolMajor, "toolMajor");
-    logAxisInfo(mRawAxes.toolMinor, "toolMinor");
-    logAxisInfo(mRawAxes.orientation, "orientation");
+void TouchInputMapper::dumpRawAxes(String8& dump) {
+    dump.append(INDENT3 "Raw Axes:\n");
+    dumpAxisInfo(dump, mRawAxes.x, "X");
+    dumpAxisInfo(dump, mRawAxes.y, "Y");
+    dumpAxisInfo(dump, mRawAxes.pressure, "Pressure");
+    dumpAxisInfo(dump, mRawAxes.touchMajor, "TouchMajor");
+    dumpAxisInfo(dump, mRawAxes.touchMinor, "TouchMinor");
+    dumpAxisInfo(dump, mRawAxes.toolMajor, "ToolMajor");
+    dumpAxisInfo(dump, mRawAxes.toolMinor, "ToolMinor");
+    dumpAxisInfo(dump, mRawAxes.orientation, "Orientation");
 }
 
 bool TouchInputMapper::configureSurfaceLocked() {
@@ -1391,10 +1426,8 @@
 
     bool sizeChanged = mLocked.surfaceWidth != width || mLocked.surfaceHeight != height;
     if (sizeChanged) {
-        LOGI("Device reconfigured (display size changed): id=0x%x, name=%s",
-                getDeviceId(), getDeviceName().string());
-        LOGI(INDENT "Width: %dpx", width);
-        LOGI(INDENT "Height: %dpx", height);
+        LOGI("Device reconfigured: id=0x%x, name=%s, display size is now %dx%d",
+                getDeviceId(), getDeviceName().string(), width, height);
 
         mLocked.surfaceWidth = width;
         mLocked.surfaceHeight = height;
@@ -1562,39 +1595,13 @@
         mLocked.orientedRanges.y.fuzz = orientedYScale;
     }
 
-    if (sizeChanged) {
-        logMotionRangesLocked();
-    }
-
     return true;
 }
 
-static void logMotionRangeInfo(InputDeviceInfo::MotionRange* range, const char* name) {
-    if (range) {
-        LOGI(INDENT "Output %s range: min=%f, max=%f, flat=%f, fuzz=%f",
-                name, range->min, range->max, range->flat, range->fuzz);
-    } else {
-        LOGI(INDENT "Output %s range: unsupported", name);
-    }
-}
-
-void TouchInputMapper::logMotionRangesLocked() {
-    logMotionRangeInfo(& mLocked.orientedRanges.x, "x");
-    logMotionRangeInfo(& mLocked.orientedRanges.y, "y");
-    logMotionRangeInfo(mLocked.orientedRanges.havePressure
-            ? & mLocked.orientedRanges.pressure : NULL, "pressure");
-    logMotionRangeInfo(mLocked.orientedRanges.haveSize
-            ? & mLocked.orientedRanges.size : NULL, "size");
-    logMotionRangeInfo(mLocked.orientedRanges.haveTouchArea
-            ? & mLocked.orientedRanges.touchMajor : NULL, "touchMajor");
-    logMotionRangeInfo(mLocked.orientedRanges.haveTouchArea
-            ? & mLocked.orientedRanges.touchMinor : NULL, "touchMinor");
-    logMotionRangeInfo(mLocked.orientedRanges.haveToolArea
-            ? & mLocked.orientedRanges.toolMajor : NULL, "toolMajor");
-    logMotionRangeInfo(mLocked.orientedRanges.haveToolArea
-            ? & mLocked.orientedRanges.toolMinor : NULL, "toolMinor");
-    logMotionRangeInfo(mLocked.orientedRanges.haveOrientation
-            ? & mLocked.orientedRanges.orientation : NULL, "orientation");
+void TouchInputMapper::dumpSurfaceLocked(String8& dump) {
+    dump.appendFormat(INDENT3 "SurfaceWidth: %dpx\n", mLocked.surfaceWidth);
+    dump.appendFormat(INDENT3 "SurfaceHeight: %dpx\n", mLocked.surfaceHeight);
+    dump.appendFormat(INDENT3 "SurfaceOrientation: %d\n", mLocked.surfaceOrientation);
 }
 
 void TouchInputMapper::configureVirtualKeysLocked() {
@@ -1651,9 +1658,21 @@
         virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight)
                 * touchScreenHeight / mLocked.surfaceHeight + touchScreenTop;
 
-        LOGI(INDENT "VirtualKey %d: keyCode=%d hitLeft=%d hitRight=%d hitTop=%d hitBottom=%d",
-                virtualKey.scanCode, virtualKey.keyCode,
-                virtualKey.hitLeft, virtualKey.hitRight, virtualKey.hitTop, virtualKey.hitBottom);
+    }
+}
+
+void TouchInputMapper::dumpVirtualKeysLocked(String8& dump) {
+    if (!mLocked.virtualKeys.isEmpty()) {
+        dump.append(INDENT3 "Virtual Keys:\n");
+
+        for (size_t i = 0; i < mLocked.virtualKeys.size(); i++) {
+            const VirtualKey& virtualKey = mLocked.virtualKeys.itemAt(i);
+            dump.appendFormat(INDENT4 "%d: scanCode=%d, keyCode=%d, "
+                    "hitLeft=%d, hitRight=%d, hitTop=%d, hitBottom=%d\n",
+                    i, virtualKey.scanCode, virtualKey.keyCode,
+                    virtualKey.hitLeft, virtualKey.hitRight,
+                    virtualKey.hitTop, virtualKey.hitBottom);
+        }
     }
 }
 
@@ -1861,19 +1880,19 @@
     }
 }
 
-void TouchInputMapper::logCalibration() {
-    LOGI(INDENT "Calibration:");
+void TouchInputMapper::dumpCalibration(String8& dump) {
+    dump.append(INDENT3 "Calibration:\n");
 
     // Touch Area
     switch (mCalibration.touchAreaCalibration) {
     case Calibration::TOUCH_AREA_CALIBRATION_NONE:
-        LOGI(INDENT INDENT "touch.touchArea.calibration: none");
+        dump.append(INDENT4 "touch.touchArea.calibration: none\n");
         break;
     case Calibration::TOUCH_AREA_CALIBRATION_GEOMETRIC:
-        LOGI(INDENT INDENT "touch.touchArea.calibration: geometric");
+        dump.append(INDENT4 "touch.touchArea.calibration: geometric\n");
         break;
     case Calibration::TOUCH_AREA_CALIBRATION_PRESSURE:
-        LOGI(INDENT INDENT "touch.touchArea.calibration: pressure");
+        dump.append(INDENT4 "touch.touchArea.calibration: pressure\n");
         break;
     default:
         assert(false);
@@ -1882,40 +1901,43 @@
     // Tool Area
     switch (mCalibration.toolAreaCalibration) {
     case Calibration::TOOL_AREA_CALIBRATION_NONE:
-        LOGI(INDENT INDENT "touch.toolArea.calibration: none");
+        dump.append(INDENT4 "touch.toolArea.calibration: none\n");
         break;
     case Calibration::TOOL_AREA_CALIBRATION_GEOMETRIC:
-        LOGI(INDENT INDENT "touch.toolArea.calibration: geometric");
+        dump.append(INDENT4 "touch.toolArea.calibration: geometric\n");
         break;
     case Calibration::TOOL_AREA_CALIBRATION_LINEAR:
-        LOGI(INDENT INDENT "touch.toolArea.calibration: linear");
+        dump.append(INDENT4 "touch.toolArea.calibration: linear\n");
         break;
     default:
         assert(false);
     }
 
     if (mCalibration.haveToolAreaLinearScale) {
-        LOGI(INDENT INDENT "touch.toolArea.linearScale: %f", mCalibration.toolAreaLinearScale);
+        dump.appendFormat(INDENT4 "touch.toolArea.linearScale: %0.3f\n",
+                mCalibration.toolAreaLinearScale);
     }
 
     if (mCalibration.haveToolAreaLinearBias) {
-        LOGI(INDENT INDENT "touch.toolArea.linearBias: %f", mCalibration.toolAreaLinearBias);
+        dump.appendFormat(INDENT4 "touch.toolArea.linearBias: %0.3f\n",
+                mCalibration.toolAreaLinearBias);
     }
 
     if (mCalibration.haveToolAreaIsSummed) {
-        LOGI(INDENT INDENT "touch.toolArea.isSummed: %d", mCalibration.toolAreaIsSummed);
+        dump.appendFormat(INDENT4 "touch.toolArea.isSummed: %d\n",
+                mCalibration.toolAreaIsSummed);
     }
 
     // Pressure
     switch (mCalibration.pressureCalibration) {
     case Calibration::PRESSURE_CALIBRATION_NONE:
-        LOGI(INDENT INDENT "touch.pressure.calibration: none");
+        dump.append(INDENT4 "touch.pressure.calibration: none\n");
         break;
     case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
-        LOGI(INDENT INDENT "touch.pressure.calibration: physical");
+        dump.append(INDENT4 "touch.pressure.calibration: physical\n");
         break;
     case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
-        LOGI(INDENT INDENT "touch.pressure.calibration: amplitude");
+        dump.append(INDENT4 "touch.pressure.calibration: amplitude\n");
         break;
     default:
         assert(false);
@@ -1923,10 +1945,10 @@
 
     switch (mCalibration.pressureSource) {
     case Calibration::PRESSURE_SOURCE_PRESSURE:
-        LOGI(INDENT INDENT "touch.pressure.source: pressure");
+        dump.append(INDENT4 "touch.pressure.source: pressure\n");
         break;
     case Calibration::PRESSURE_SOURCE_TOUCH:
-        LOGI(INDENT INDENT "touch.pressure.source: touch");
+        dump.append(INDENT4 "touch.pressure.source: touch\n");
         break;
     case Calibration::PRESSURE_SOURCE_DEFAULT:
         break;
@@ -1935,16 +1957,17 @@
     }
 
     if (mCalibration.havePressureScale) {
-        LOGI(INDENT INDENT "touch.pressure.scale: %f", mCalibration.pressureScale);
+        dump.appendFormat(INDENT4 "touch.pressure.scale: %0.3f\n",
+                mCalibration.pressureScale);
     }
 
     // Size
     switch (mCalibration.sizeCalibration) {
     case Calibration::SIZE_CALIBRATION_NONE:
-        LOGI(INDENT INDENT "touch.size.calibration: none");
+        dump.append(INDENT4 "touch.size.calibration: none\n");
         break;
     case Calibration::SIZE_CALIBRATION_NORMALIZED:
-        LOGI(INDENT INDENT "touch.size.calibration: normalized");
+        dump.append(INDENT4 "touch.size.calibration: normalized\n");
         break;
     default:
         assert(false);
@@ -1953,10 +1976,10 @@
     // Orientation
     switch (mCalibration.orientationCalibration) {
     case Calibration::ORIENTATION_CALIBRATION_NONE:
-        LOGI(INDENT INDENT "touch.orientation.calibration: none");
+        dump.append(INDENT4 "touch.orientation.calibration: none\n");
         break;
     case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
-        LOGI(INDENT INDENT "touch.orientation.calibration: interpolated");
+        dump.append(INDENT4 "touch.orientation.calibration: interpolated\n");
         break;
     default:
         assert(false);
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index 890786e..587c8ff 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -316,6 +316,7 @@
         mNewPosition = mCblk->server + mUpdatePeriod;
         mCblk->bufferTimeoutMs = MAX_STARTUP_TIMEOUT_MS;
         mCblk->waitTimeMs = 0;
+        mCblk->flags &= ~CBLK_DISABLED_ON;
         if (t != 0) {
            t->run("AudioTrackThread", THREAD_PRIORITY_AUDIO_CLIENT);
         } else {
@@ -842,6 +843,13 @@
         cblk->lock.unlock();
     }
 
+    // restart track if it was disabled by audioflinger due to previous underrun
+    if (cblk->flags & CBLK_DISABLED_MSK) {
+        cblk->flags &= ~CBLK_DISABLED_ON;
+        LOGW("obtainBuffer() track %p disabled, restarting", this);
+        mAudioTrack->start();
+    }
+
     cblk->waitTimeMs = 0;
 
     if (framesReq > framesAvail) {
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 97c9003..8e50d39 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -276,20 +276,6 @@
 
 status_t AwesomePlayer::setDataSource(
         int fd, int64_t offset, int64_t length) {
-#if 0
-    // return setDataSource("httplive://qthttp.apple.com.edgesuite.net/1009qpeijrfn/sl.m3u8");
-    return setDataSource("httplive://qthttp.apple.com.edgesuite.net/1009qpeijrfn/0440.m3u8");
-    // return setDataSource("httplive://qthttp.apple.com.edgesuite.net/1009qpeijrfn/0640.m3u8");
-    // return setDataSource("httplive://qthttp.apple.com.edgesuite.net/1009qpeijrfn/1240_vod.m3u8");
-    // return setDataSource("httplive://iphoned5.akamai.com.edgesuite.net/mhbarron/nasatv/nasatv_96.m3u8");
-    // return setDataSource("httplive://iphoned5.akamai.com.edgesuite.net/mhbarron/nasatv/nasatv_1500.m3u8");
-    // return setDataSource("httplive://iphone.video.hsn.com/iPhone_high.m3u8");
-    // return setDataSource("httplive://iphoned5.akamai.com.edgesuite.net/mhbarron/iphonewebcast/webcast090209_all/webcast090209_all.m3u8");
-    // return setDataSource("httplive://qthttp.akamai.com.edgesuite.net/iphone_demo/Video_Content/usat/tt_062209_iphone/hi/prog_index.m3u8");
-    // return setDataSource("httplive://qthttp.akamai.com.edgesuite.net/iphone_demo/Video_Content/usat/tt_googmaps/hi/prog_index.m3u8");
-    // return setDataSource("httplive://qthttp.akamai.com.edgesuite.net/iphone_demo/Video_Content/mtv/ni_spo_25a_rt74137_clip_syn/hi/prog_index.m3u8");
-#endif
-
     Mutex::Autolock autoLock(mLock);
 
     reset_l();
diff --git a/media/libstagefright/avc_utils.cpp b/media/libstagefright/avc_utils.cpp
index 511ae12..a8f1104 100644
--- a/media/libstagefright/avc_utils.cpp
+++ b/media/libstagefright/avc_utils.cpp
@@ -21,7 +21,7 @@
 
 namespace android {
 
-static unsigned parseUE(ABitReader *br) {
+unsigned parseUE(ABitReader *br) {
     unsigned numZeroes = 0;
     while (br->getBits(1) == 0) {
         ++numZeroes;
diff --git a/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp b/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp
index 3c0b736..868c514 100644
--- a/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp
+++ b/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp
@@ -31,6 +31,7 @@
 #include <media/stagefright/MediaErrors.h>
 #include <media/stagefright/MetaData.h>
 #include <media/stagefright/Utils.h>
+#include <media/stagefright/foundation/hexdump.h>
 
 namespace android {
 
diff --git a/media/libstagefright/httplive/LiveSource.cpp b/media/libstagefright/httplive/LiveSource.cpp
index 001afc4..9103927 100644
--- a/media/libstagefright/httplive/LiveSource.cpp
+++ b/media/libstagefright/httplive/LiveSource.cpp
@@ -93,7 +93,7 @@
         }
 
         if (mLastFetchTimeUs < 0) {
-            mPlaylistIndex = mPlaylist->size() / 2;
+            mPlaylistIndex = 0;
         } else {
             if (nextSequenceNumber < mFirstItemSequenceNumber
                     || nextSequenceNumber
diff --git a/media/libstagefright/include/avc_utils.h b/media/libstagefright/include/avc_utils.h
index cc405b5..6602852 100644
--- a/media/libstagefright/include/avc_utils.h
+++ b/media/libstagefright/include/avc_utils.h
@@ -22,9 +22,13 @@
 
 namespace android {
 
+struct ABitReader;
+
 void FindAVCDimensions(
         const sp<ABuffer> &seqParamSet, int32_t *width, int32_t *height);
 
+unsigned parseUE(ABitReader *br);
+
 }  // namespace android
 
 #endif  // AVC_UTILS_H_
diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp
index 47cca80..bcaab9f 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.cpp
+++ b/media/libstagefright/mpeg2ts/ATSParser.cpp
@@ -21,6 +21,7 @@
 #include "ATSParser.h"
 
 #include "AnotherPacketSource.h"
+#include "ESQueue.h"
 #include "include/avc_utils.h"
 
 #include <media/stagefright/foundation/ABitReader.h>
@@ -79,6 +80,8 @@
     sp<AnotherPacketSource> mSource;
     bool mPayloadStarted;
 
+    ElementaryStreamQueue mQueue;
+
     void flush();
     void parsePES(ABitReader *br);
 
@@ -232,7 +235,9 @@
     : mElementaryPID(elementaryPID),
       mStreamType(streamType),
       mBuffer(new ABuffer(128 * 1024)),
-      mPayloadStarted(false) {
+      mPayloadStarted(false),
+      mQueue(streamType == 0x1b
+              ? ElementaryStreamQueue::H264 : ElementaryStreamQueue::AAC) {
     mBuffer->setRange(0, 0);
 }
 
@@ -433,373 +438,31 @@
     mBuffer->setRange(0, 0);
 }
 
-static sp<ABuffer> FindNAL(
-        const uint8_t *data, size_t size, unsigned nalType,
-        size_t *stopOffset) {
-    bool foundStart = false;
-    size_t startOffset = 0;
-
-    size_t offset = 0;
-    for (;;) {
-        while (offset + 3 < size
-                && memcmp("\x00\x00\x00\x01", &data[offset], 4)) {
-            ++offset;
-        }
-
-        if (foundStart) {
-            size_t nalSize;
-            if (offset + 3 >= size) {
-                nalSize = size - startOffset;
-            } else {
-                nalSize = offset - startOffset;
-            }
-
-            sp<ABuffer> nal = new ABuffer(nalSize);
-            memcpy(nal->data(), &data[startOffset], nalSize);
-
-            if (stopOffset != NULL) {
-                *stopOffset = startOffset + nalSize;
-            }
-
-            return nal;
-        }
-
-        if (offset + 4 >= size) {
-            return NULL;
-        }
-
-        if ((data[offset + 4] & 0x1f) == nalType) {
-            foundStart = true;
-            startOffset = offset + 4;
-        }
-
-        offset += 4;
-    }
-}
-
-static sp<ABuffer> MakeAVCCodecSpecificData(
-        const sp<ABuffer> &buffer, int32_t *width, int32_t *height) {
-    const uint8_t *data = buffer->data();
-    size_t size = buffer->size();
-
-    sp<ABuffer> seqParamSet = FindNAL(data, size, 7, NULL);
-    if (seqParamSet == NULL) {
-        return NULL;
-    }
-
-    FindAVCDimensions(seqParamSet, width, height);
-
-    size_t stopOffset;
-    sp<ABuffer> picParamSet = FindNAL(data, size, 8, &stopOffset);
-    CHECK(picParamSet != NULL);
-
-    buffer->setRange(stopOffset, size - stopOffset);
-    LOGV("buffer has %d bytes left.", buffer->size());
-
-    size_t csdSize =
-        1 + 3 + 1 + 1
-        + 2 * 1 + seqParamSet->size()
-        + 1 + 2 * 1 + picParamSet->size();
-
-    sp<ABuffer> csd = new ABuffer(csdSize);
-    uint8_t *out = csd->data();
-
-    *out++ = 0x01;  // configurationVersion
-    memcpy(out, seqParamSet->data() + 1, 3);  // profile/level...
-    out += 3;
-    *out++ = (0x3f << 2) | 1;  // lengthSize == 2 bytes
-    *out++ = 0xe0 | 1;
-
-    *out++ = seqParamSet->size() >> 8;
-    *out++ = seqParamSet->size() & 0xff;
-    memcpy(out, seqParamSet->data(), seqParamSet->size());
-    out += seqParamSet->size();
-
-    *out++ = 1;
-
-    *out++ = picParamSet->size() >> 8;
-    *out++ = picParamSet->size() & 0xff;
-    memcpy(out, picParamSet->data(), picParamSet->size());
-
-    return csd;
-}
-
-static bool getNextNALUnit(
-        const uint8_t **_data, size_t *_size,
-        const uint8_t **nalStart, size_t *nalSize) {
-    const uint8_t *data = *_data;
-    size_t size = *_size;
-
-    // hexdump(data, size);
-
-    *nalStart = NULL;
-    *nalSize = 0;
-
-    if (size == 0) {
-        return false;
-    }
-
-    size_t offset = 0;
-    for (;;) {
-        CHECK_LT(offset + 2, size);
-
-        if (!memcmp("\x00\x00\x01", &data[offset], 3)) {
-            break;
-        }
-
-        CHECK_EQ((unsigned)data[offset], 0x00u);
-        ++offset;
-    }
-
-    offset += 3;
-    size_t startOffset = offset;
-
-    while (offset + 2 < size
-            && memcmp("\x00\x00\x00", &data[offset], 3)
-            && memcmp("\x00\x00\x01", &data[offset], 3)) {
-        ++offset;
-    }
-
-    if (offset + 2 >= size) {
-        *nalStart = &data[startOffset];
-        *nalSize = size - startOffset;
-
-        *_data = NULL;
-        *_size = 0;
-
-        return true;
-    }
-
-    size_t endOffset = offset;
-
-    while (offset + 2 < size && memcmp("\x00\x00\x01", &data[offset], 3)) {
-        CHECK_EQ((unsigned)data[offset], 0x00u);
-        ++offset;
-    }
-
-    *nalStart = &data[startOffset];
-    *nalSize = endOffset - startOffset;
-
-    if (offset + 2 < size) {
-        *_data = &data[offset];
-        *_size = size - offset;
-    } else {
-        *_data = NULL;
-        *_size = 0;
-    }
-
-    return true;
-}
-
-sp<ABuffer> MakeCleanAVCData(const uint8_t *data, size_t size) {
-    // hexdump(data, size);
-
-    const uint8_t *tmpData = data;
-    size_t tmpSize = size;
-
-    size_t totalSize = 0;
-    const uint8_t *nalStart;
-    size_t nalSize;
-    while (getNextNALUnit(&tmpData, &tmpSize, &nalStart, &nalSize)) {
-        // hexdump(nalStart, nalSize);
-        totalSize += 4 + nalSize;
-    }
-
-    sp<ABuffer> buffer = new ABuffer(totalSize);
-    size_t offset = 0;
-    while (getNextNALUnit(&data, &size, &nalStart, &nalSize)) {
-        memcpy(buffer->data() + offset, "\x00\x00\x00\x01", 4);
-        memcpy(buffer->data() + offset + 4, nalStart, nalSize);
-
-        offset += 4 + nalSize;
-    }
-
-    return buffer;
-}
-
-static sp<ABuffer> FindMPEG2ADTSConfig(
-        const sp<ABuffer> &buffer, int32_t *sampleRate, int32_t *channelCount) {
-    ABitReader br(buffer->data(), buffer->size());
-
-    CHECK_EQ(br.getBits(12), 0xfffu);
-    CHECK_EQ(br.getBits(1), 0u);
-    CHECK_EQ(br.getBits(2), 0u);
-    br.getBits(1);  // protection_absent
-    unsigned profile = br.getBits(2);
-    LOGV("profile = %u", profile);
-    CHECK_NE(profile, 3u);
-    unsigned sampling_freq_index = br.getBits(4);
-    br.getBits(1);  // private_bit
-    unsigned channel_configuration = br.getBits(3);
-    CHECK_NE(channel_configuration, 0u);
-
-    LOGV("sampling_freq_index = %u", sampling_freq_index);
-    LOGV("channel_configuration = %u", channel_configuration);
-
-    CHECK_LE(sampling_freq_index, 11u);
-    static const int32_t kSamplingFreq[] = {
-        96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
-        16000, 12000, 11025, 8000
-    };
-    *sampleRate = kSamplingFreq[sampling_freq_index];
-
-    *channelCount = channel_configuration;
-
-    static const uint8_t kStaticESDS[] = {
-        0x03, 22,
-        0x00, 0x00,     // ES_ID
-        0x00,           // streamDependenceFlag, URL_Flag, OCRstreamFlag
-
-        0x04, 17,
-        0x40,                       // Audio ISO/IEC 14496-3
-        0x00, 0x00, 0x00, 0x00,
-        0x00, 0x00, 0x00, 0x00,
-        0x00, 0x00, 0x00, 0x00,
-
-        0x05, 2,
-        // AudioSpecificInfo follows
-
-        // oooo offf fccc c000
-        // o - audioObjectType
-        // f - samplingFreqIndex
-        // c - channelConfig
-    };
-    sp<ABuffer> csd = new ABuffer(sizeof(kStaticESDS) + 2);
-    memcpy(csd->data(), kStaticESDS, sizeof(kStaticESDS));
-
-    csd->data()[sizeof(kStaticESDS)] =
-        ((profile + 1) << 3) | (sampling_freq_index >> 1);
-
-    csd->data()[sizeof(kStaticESDS) + 1] =
-        ((sampling_freq_index << 7) & 0x80) | (channel_configuration << 3);
-
-    // hexdump(csd->data(), csd->size());
-    return csd;
-}
-
 void ATSParser::Stream::onPayloadData(
         unsigned PTS_DTS_flags, uint64_t PTS, uint64_t DTS,
         const uint8_t *data, size_t size) {
     LOGV("onPayloadData mStreamType=0x%02x", mStreamType);
 
-    sp<ABuffer> buffer;
-
-    if (mStreamType == 0x1b) {
-        buffer = MakeCleanAVCData(data, size);
-    } else {
-        // hexdump(data, size);
-
-        buffer = new ABuffer(size);
-        memcpy(buffer->data(), data, size);
-    }
-
-    if (mSource == NULL) {
-        sp<MetaData> meta = new MetaData;
-
-        if (mStreamType == 0x1b) {
-            meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
-
-            int32_t width, height;
-            sp<ABuffer> csd = MakeAVCCodecSpecificData(buffer, &width, &height);
-
-            if (csd == NULL) {
-                return;
-            }
-
-            meta->setData(kKeyAVCC, 0, csd->data(), csd->size());
-            meta->setInt32(kKeyWidth, width);
-            meta->setInt32(kKeyHeight, height);
-        } else {
-            CHECK_EQ(mStreamType, 0x0fu);
-
-            meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC);
-
-            int32_t sampleRate, channelCount;
-            sp<ABuffer> csd =
-                FindMPEG2ADTSConfig(buffer, &sampleRate, &channelCount);
-
-            LOGV("sampleRate = %d", sampleRate);
-            LOGV("channelCount = %d", channelCount);
-
-            meta->setInt32(kKeySampleRate, sampleRate);
-            meta->setInt32(kKeyChannelCount, channelCount);
-
-            meta->setData(kKeyESDS, 0, csd->data(), csd->size());
-        }
-
-        LOGV("created source!");
-        mSource = new AnotherPacketSource(meta);
-
-        // fall through
-    }
-
     CHECK(PTS_DTS_flags == 2 || PTS_DTS_flags == 3);
-    buffer->meta()->setInt64("time", (PTS * 100) / 9);
+    int64_t timeUs = (PTS * 100) / 9;
 
-    if (mStreamType == 0x0f) {
-        extractAACFrames(buffer);
-    }
+    status_t err = mQueue.appendData(data, size, timeUs);
+    CHECK_EQ(err, (status_t)OK);
 
-    mSource->queueAccessUnit(buffer);
-}
+    sp<ABuffer> accessUnit;
+    while ((accessUnit = mQueue.dequeueAccessUnit()) != NULL) {
+        if (mSource == NULL) {
+            sp<MetaData> meta = mQueue.getFormat();
 
-// Disassemble one or more ADTS frames into their constituent parts and
-// leave only the concatenated raw_data_blocks in the buffer.
-void ATSParser::Stream::extractAACFrames(const sp<ABuffer> &buffer) {
-    size_t dstOffset = 0;
-
-    size_t offset = 0;
-    while (offset < buffer->size()) {
-        CHECK_LE(offset + 7, buffer->size());
-
-        ABitReader bits(buffer->data() + offset, buffer->size() - offset);
-
-        // adts_fixed_header
-
-        CHECK_EQ(bits.getBits(12), 0xfffu);
-        bits.skipBits(3);  // ID, layer
-        bool protection_absent = bits.getBits(1) != 0;
-
-        // profile_ObjectType, sampling_frequency_index, private_bits,
-        // channel_configuration, original_copy, home
-        bits.skipBits(12);
-
-        // adts_variable_header
-
-        // copyright_identification_bit, copyright_identification_start
-        bits.skipBits(2);
-
-        unsigned aac_frame_length = bits.getBits(13);
-
-        bits.skipBits(11);  // adts_buffer_fullness
-
-        unsigned number_of_raw_data_blocks_in_frame = bits.getBits(2);
-
-        if (number_of_raw_data_blocks_in_frame == 0) {
-            size_t scan = offset + aac_frame_length;
-
-            offset += 7;
-            if (!protection_absent) {
-                offset += 2;
+            if (meta != NULL) {
+                LOGV("created source!");
+                mSource = new AnotherPacketSource(meta);
+                mSource->queueAccessUnit(accessUnit);
             }
-
-            CHECK_LE(scan, buffer->size());
-
-            LOGV("found aac raw data block at [0x%08x ; 0x%08x)", offset, scan);
-
-            memmove(&buffer->data()[dstOffset], &buffer->data()[offset],
-                    scan - offset);
-
-            dstOffset += scan - offset;
-            offset = scan;
         } else {
-            // To be implemented.
-            TRESPASS();
+            mSource->queueAccessUnit(accessUnit);
         }
     }
-    CHECK_EQ(offset, buffer->size());
-
-    buffer->setRange(buffer->offset(), dstOffset);
 }
 
 sp<MediaSource> ATSParser::Stream::getSource(SourceType type) {
diff --git a/media/libstagefright/mpeg2ts/Android.mk b/media/libstagefright/mpeg2ts/Android.mk
index 3544b4c..4dfc0f7 100644
--- a/media/libstagefright/mpeg2ts/Android.mk
+++ b/media/libstagefright/mpeg2ts/Android.mk
@@ -5,6 +5,7 @@
 LOCAL_SRC_FILES:=                 \
         AnotherPacketSource.cpp   \
         ATSParser.cpp             \
+        ESQueue.cpp               \
         MPEG2TSExtractor.cpp      \
 
 LOCAL_C_INCLUDES:= \
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
new file mode 100644
index 0000000..d87040b
--- /dev/null
+++ b/media/libstagefright/mpeg2ts/ESQueue.cpp
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "ESQueue"
+#include <media/stagefright/foundation/ADebug.h>
+
+#include "ESQueue.h"
+
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/foundation/ABitReader.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+
+#include "include/avc_utils.h"
+
+namespace android {
+
+ElementaryStreamQueue::ElementaryStreamQueue(Mode mode)
+    : mMode(mode) {
+}
+
+sp<MetaData> ElementaryStreamQueue::getFormat() {
+    return mFormat;
+}
+
+static status_t getNextNALUnit(
+        const uint8_t **_data, size_t *_size,
+        const uint8_t **nalStart, size_t *nalSize,
+        bool startCodeFollows = false) {
+    const uint8_t *data = *_data;
+    size_t size = *_size;
+
+    *nalStart = NULL;
+    *nalSize = 0;
+
+    if (size == 0) {
+        return -EAGAIN;
+    }
+
+    // Skip any number of leading 0x00.
+
+    size_t offset = 0;
+    while (offset < size && data[offset] == 0x00) {
+        ++offset;
+    }
+
+    if (offset == size) {
+        return -EAGAIN;
+    }
+
+    // A valid startcode consists of at least two 0x00 bytes followed by 0x01.
+
+    if (offset < 2 || data[offset] != 0x01) {
+        return ERROR_MALFORMED;
+    }
+
+    ++offset;
+
+    size_t startOffset = offset;
+
+    for (;;) {
+        while (offset < size && data[offset] != 0x01) {
+            ++offset;
+        }
+
+        if (offset == size) {
+            if (startCodeFollows) {
+                offset = size + 2;
+                break;
+            }
+
+            return -EAGAIN;
+        }
+
+        if (data[offset - 1] == 0x00 && data[offset - 2] == 0x00) {
+            break;
+        }
+
+        ++offset;
+    }
+
+    size_t endOffset = offset - 2;
+    while (data[endOffset - 1] == 0x00) {
+        --endOffset;
+    }
+
+    *nalStart = &data[startOffset];
+    *nalSize = endOffset - startOffset;
+
+    if (offset + 2 < size) {
+        *_data = &data[offset - 2];
+        *_size = size - offset + 2;
+    } else {
+        *_data = NULL;
+        *_size = 0;
+    }
+
+    return OK;
+}
+
+status_t ElementaryStreamQueue::appendData(
+        const void *data, size_t size, int64_t timeUs) {
+    if (mBuffer == NULL || mBuffer->size() == 0) {
+        switch (mMode) {
+            case H264:
+            {
+                if (size < 4 || memcmp("\x00\x00\x00\x01", data, 4)) {
+                    return ERROR_MALFORMED;
+                }
+                break;
+            }
+
+            case AAC:
+            {
+                uint8_t *ptr = (uint8_t *)data;
+
+                if (size < 2 || ptr[0] != 0xff || (ptr[1] >> 4) != 0x0f) {
+                    return ERROR_MALFORMED;
+                }
+                break;
+            }
+
+            default:
+                TRESPASS();
+                break;
+        }
+    }
+
+    size_t neededSize = (mBuffer == NULL ? 0 : mBuffer->size()) + size;
+    if (mBuffer == NULL || neededSize > mBuffer->capacity()) {
+        neededSize = (neededSize + 65535) & ~65535;
+
+        LOGI("resizing buffer to size %d", neededSize);
+
+        sp<ABuffer> buffer = new ABuffer(neededSize);
+        if (mBuffer != NULL) {
+            memcpy(buffer->data(), mBuffer->data(), mBuffer->size());
+            buffer->setRange(0, mBuffer->size());
+        } else {
+            buffer->setRange(0, 0);
+        }
+
+        mBuffer = buffer;
+    }
+
+    memcpy(mBuffer->data() + mBuffer->size(), data, size);
+    mBuffer->setRange(0, mBuffer->size() + size);
+
+    mTimestamps.push_back(timeUs);
+
+    return OK;
+}
+
+sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnit() {
+    if (mMode == H264) {
+        return dequeueAccessUnitH264();
+    } else {
+        CHECK_EQ((unsigned)mMode, (unsigned)AAC);
+        return dequeueAccessUnitAAC();
+    }
+}
+
+sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitAAC() {
+    Vector<size_t> frameOffsets;
+    Vector<size_t> frameSizes;
+    size_t auSize = 0;
+
+    size_t offset = 0;
+    while (offset + 7 <= mBuffer->size()) {
+        ABitReader bits(mBuffer->data() + offset, mBuffer->size() - offset);
+
+        // adts_fixed_header
+
+        CHECK_EQ(bits.getBits(12), 0xfffu);
+        bits.skipBits(3);  // ID, layer
+        bool protection_absent = bits.getBits(1) != 0;
+
+        if (mFormat == NULL) {
+            unsigned profile = bits.getBits(2);
+            CHECK_NE(profile, 3u);
+            unsigned sampling_freq_index = bits.getBits(4);
+            bits.getBits(1);  // private_bit
+            unsigned channel_configuration = bits.getBits(3);
+            CHECK_NE(channel_configuration, 0u);
+            bits.skipBits(2);  // original_copy, home
+
+            mFormat = MakeAACCodecSpecificData(
+                    profile, sampling_freq_index, channel_configuration);
+        } else {
+            // profile_ObjectType, sampling_frequency_index, private_bits,
+            // channel_configuration, original_copy, home
+            bits.skipBits(12);
+        }
+
+        // adts_variable_header
+
+        // copyright_identification_bit, copyright_identification_start
+        bits.skipBits(2);
+
+        unsigned aac_frame_length = bits.getBits(13);
+
+        bits.skipBits(11);  // adts_buffer_fullness
+
+        unsigned number_of_raw_data_blocks_in_frame = bits.getBits(2);
+
+        if (number_of_raw_data_blocks_in_frame != 0) {
+            // To be implemented.
+            TRESPASS();
+        }
+
+        if (offset + aac_frame_length > mBuffer->size()) {
+            break;
+        }
+
+        size_t headerSize = protection_absent ? 7 : 9;
+
+        frameOffsets.push(offset + headerSize);
+        frameSizes.push(aac_frame_length - headerSize);
+        auSize += aac_frame_length - headerSize;
+
+        offset += aac_frame_length;
+    }
+
+    if (offset == 0) {
+        return NULL;
+    }
+
+    sp<ABuffer> accessUnit = new ABuffer(auSize);
+    size_t dstOffset = 0;
+    for (size_t i = 0; i < frameOffsets.size(); ++i) {
+        memcpy(accessUnit->data() + dstOffset,
+               mBuffer->data() + frameOffsets.itemAt(i),
+               frameSizes.itemAt(i));
+
+        dstOffset += frameSizes.itemAt(i);
+    }
+
+    memmove(mBuffer->data(), mBuffer->data() + offset,
+            mBuffer->size() - offset);
+    mBuffer->setRange(0, mBuffer->size() - offset);
+
+    CHECK_GT(mTimestamps.size(), 0u);
+    int64_t timeUs = *mTimestamps.begin();
+    mTimestamps.erase(mTimestamps.begin());
+
+    accessUnit->meta()->setInt64("time", timeUs);
+
+    return accessUnit;
+}
+
+// static
+sp<MetaData> ElementaryStreamQueue::MakeAACCodecSpecificData(
+        unsigned profile, unsigned sampling_freq_index,
+        unsigned channel_configuration) {
+    sp<MetaData> meta = new MetaData;
+    meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC);
+
+    CHECK_LE(sampling_freq_index, 11u);
+    static const int32_t kSamplingFreq[] = {
+        96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
+        16000, 12000, 11025, 8000
+    };
+    meta->setInt32(kKeySampleRate, kSamplingFreq[sampling_freq_index]);
+    meta->setInt32(kKeyChannelCount, channel_configuration);
+
+    static const uint8_t kStaticESDS[] = {
+        0x03, 22,
+        0x00, 0x00,     // ES_ID
+        0x00,           // streamDependenceFlag, URL_Flag, OCRstreamFlag
+
+        0x04, 17,
+        0x40,                       // Audio ISO/IEC 14496-3
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00,
+
+        0x05, 2,
+        // AudioSpecificInfo follows
+
+        // oooo offf fccc c000
+        // o - audioObjectType
+        // f - samplingFreqIndex
+        // c - channelConfig
+    };
+    sp<ABuffer> csd = new ABuffer(sizeof(kStaticESDS) + 2);
+    memcpy(csd->data(), kStaticESDS, sizeof(kStaticESDS));
+
+    csd->data()[sizeof(kStaticESDS)] =
+        ((profile + 1) << 3) | (sampling_freq_index >> 1);
+
+    csd->data()[sizeof(kStaticESDS) + 1] =
+        ((sampling_freq_index << 7) & 0x80) | (channel_configuration << 3);
+
+    meta->setData(kKeyESDS, 0, csd->data(), csd->size());
+
+    return meta;
+}
+
+struct NALPosition {
+    size_t nalOffset;
+    size_t nalSize;
+};
+
+sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitH264() {
+    const uint8_t *data = mBuffer->data();
+    size_t size = mBuffer->size();
+
+    Vector<NALPosition> nals;
+
+    size_t totalSize = 0;
+
+    status_t err;
+    const uint8_t *nalStart;
+    size_t nalSize;
+    bool foundSlice = false;
+    while ((err = getNextNALUnit(&data, &size, &nalStart, &nalSize)) == OK) {
+        CHECK_GT(nalSize, 0u);
+
+        unsigned nalType = nalStart[0] & 0x1f;
+        bool flush = false;
+
+        if (nalType == 1 || nalType == 5) {
+            if (foundSlice) {
+                ABitReader br(nalStart + 1, nalSize);
+                unsigned first_mb_in_slice = parseUE(&br);
+
+                if (first_mb_in_slice == 0) {
+                    // This slice starts a new frame.
+
+                    flush = true;
+                }
+            }
+
+            foundSlice = true;
+        } else if ((nalType == 9 || nalType == 7) && foundSlice) {
+            // Access unit delimiter and SPS will be associated with the
+            // next frame.
+
+            flush = true;
+        }
+
+        if (flush) {
+            // The access unit will contain all nal units up to, but excluding
+            // the current one, separated by 0x00 0x00 0x00 0x01 startcodes.
+
+            size_t auSize = 4 * nals.size() + totalSize;
+            sp<ABuffer> accessUnit = new ABuffer(auSize);
+
+#if !LOG_NDEBUG
+            AString out;
+#endif
+
+            size_t dstOffset = 0;
+            for (size_t i = 0; i < nals.size(); ++i) {
+                const NALPosition &pos = nals.itemAt(i);
+
+                unsigned nalType = mBuffer->data()[pos.nalOffset] & 0x1f;
+
+#if !LOG_NDEBUG
+                char tmp[128];
+                sprintf(tmp, "0x%02x", nalType);
+                if (i > 0) {
+                    out.append(", ");
+                }
+                out.append(tmp);
+#endif
+
+                memcpy(accessUnit->data() + dstOffset, "\x00\x00\x00\x01", 4);
+
+                memcpy(accessUnit->data() + dstOffset + 4,
+                       mBuffer->data() + pos.nalOffset,
+                       pos.nalSize);
+
+                dstOffset += pos.nalSize + 4;
+            }
+
+            LOGV("accessUnit contains nal types %s", out.c_str());
+
+            const NALPosition &pos = nals.itemAt(nals.size() - 1);
+            size_t nextScan = pos.nalOffset + pos.nalSize;
+
+            memmove(mBuffer->data(),
+                    mBuffer->data() + nextScan,
+                    mBuffer->size() - nextScan);
+
+            mBuffer->setRange(0, mBuffer->size() - nextScan);
+
+            CHECK_GT(mTimestamps.size(), 0u);
+            int64_t timeUs = *mTimestamps.begin();
+            mTimestamps.erase(mTimestamps.begin());
+
+            accessUnit->meta()->setInt64("time", timeUs);
+
+            if (mFormat == NULL) {
+                mFormat = MakeAVCCodecSpecificData(accessUnit);
+            }
+
+            return accessUnit;
+        }
+
+        NALPosition pos;
+        pos.nalOffset = nalStart - mBuffer->data();
+        pos.nalSize = nalSize;
+
+        nals.push(pos);
+
+        totalSize += nalSize;
+    }
+    CHECK_EQ(err, (status_t)-EAGAIN);
+
+    return NULL;
+}
+
+static sp<ABuffer> FindNAL(
+        const uint8_t *data, size_t size, unsigned nalType,
+        size_t *stopOffset) {
+    const uint8_t *nalStart;
+    size_t nalSize;
+    while (getNextNALUnit(&data, &size, &nalStart, &nalSize, true) == OK) {
+        if ((nalStart[0] & 0x1f) == nalType) {
+            sp<ABuffer> buffer = new ABuffer(nalSize);
+            memcpy(buffer->data(), nalStart, nalSize);
+            return buffer;
+        }
+    }
+
+    return NULL;
+}
+
+sp<MetaData> ElementaryStreamQueue::MakeAVCCodecSpecificData(
+        const sp<ABuffer> &accessUnit) {
+    const uint8_t *data = accessUnit->data();
+    size_t size = accessUnit->size();
+
+    sp<ABuffer> seqParamSet = FindNAL(data, size, 7, NULL);
+    if (seqParamSet == NULL) {
+        return NULL;
+    }
+
+    int32_t width, height;
+    FindAVCDimensions(seqParamSet, &width, &height);
+
+    size_t stopOffset;
+    sp<ABuffer> picParamSet = FindNAL(data, size, 8, &stopOffset);
+    CHECK(picParamSet != NULL);
+
+    size_t csdSize =
+        1 + 3 + 1 + 1
+        + 2 * 1 + seqParamSet->size()
+        + 1 + 2 * 1 + picParamSet->size();
+
+    sp<ABuffer> csd = new ABuffer(csdSize);
+    uint8_t *out = csd->data();
+
+    *out++ = 0x01;  // configurationVersion
+    memcpy(out, seqParamSet->data() + 1, 3);  // profile/level...
+    out += 3;
+    *out++ = (0x3f << 2) | 1;  // lengthSize == 2 bytes
+    *out++ = 0xe0 | 1;
+
+    *out++ = seqParamSet->size() >> 8;
+    *out++ = seqParamSet->size() & 0xff;
+    memcpy(out, seqParamSet->data(), seqParamSet->size());
+    out += seqParamSet->size();
+
+    *out++ = 1;
+
+    *out++ = picParamSet->size() >> 8;
+    *out++ = picParamSet->size() & 0xff;
+    memcpy(out, picParamSet->data(), picParamSet->size());
+
+#if 0
+    LOGI("AVC seq param set");
+    hexdump(seqParamSet->data(), seqParamSet->size());
+#endif
+
+    sp<MetaData> meta = new MetaData;
+    meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
+
+    meta->setData(kKeyAVCC, 0, csd->data(), csd->size());
+    meta->setInt32(kKeyWidth, width);
+    meta->setInt32(kKeyHeight, height);
+
+    return meta;
+}
+
+}  // namespace android
diff --git a/media/libstagefright/mpeg2ts/ESQueue.h b/media/libstagefright/mpeg2ts/ESQueue.h
new file mode 100644
index 0000000..d2e87f2
--- /dev/null
+++ b/media/libstagefright/mpeg2ts/ESQueue.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ES_QUEUE_H_
+
+#define ES_QUEUE_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/List.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct ABuffer;
+struct MetaData;
+
+struct ElementaryStreamQueue {
+    enum Mode {
+        H264,
+        AAC
+    };
+    ElementaryStreamQueue(Mode mode);
+
+    status_t appendData(const void *data, size_t size, int64_t timeUs);
+
+    sp<ABuffer> dequeueAccessUnit();
+
+    sp<MetaData> getFormat();
+
+private:
+    Mode mMode;
+
+    sp<ABuffer> mBuffer;
+    List<int64_t> mTimestamps;
+
+    sp<MetaData> mFormat;
+
+    sp<ABuffer> dequeueAccessUnitH264();
+    sp<ABuffer> dequeueAccessUnitAAC();
+
+    static sp<MetaData> MakeAACCodecSpecificData(
+            unsigned profile, unsigned sampling_freq_index,
+            unsigned channel_configuration);
+
+    static sp<MetaData> MakeAVCCodecSpecificData(
+            const sp<ABuffer> &accessUnit);
+
+    DISALLOW_EVIL_CONSTRUCTORS(ElementaryStreamQueue);
+};
+
+}  // namespace android
+
+#endif  // ES_QUEUE_H_
diff --git a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
index 2417305..c5257bb 100644
--- a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
+++ b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
@@ -157,7 +157,7 @@
             }
         }
 
-        if (++numPacketsParsed > 1500) {
+        if (++numPacketsParsed > 2500) {
             break;
         }
     }
diff --git a/native/include/android/configuration.h b/native/include/android/configuration.h
index 79b9b1e..99e8f97 100644
--- a/native/include/android/configuration.h
+++ b/native/include/android/configuration.h
@@ -79,8 +79,8 @@
     ACONFIGURATION_UI_MODE_TYPE_CAR = 0x03,
 
     ACONFIGURATION_UI_MODE_NIGHT_ANY = 0x00,
-    ACONFIGURATION_UI_MODE_NIGHT_NO = 0x10,
-    ACONFIGURATION_UI_MODE_NIGHT_YES = 0x20,
+    ACONFIGURATION_UI_MODE_NIGHT_NO = 0x1,
+    ACONFIGURATION_UI_MODE_NIGHT_YES = 0x2,
 
     ACONFIGURATION_MCC = 0x0001,
     ACONFIGURATION_MNC = 0x0002,
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index eb86277..ce10f5b 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -159,7 +159,7 @@
             try {
                 return ObbScanner.getObbInfo(filename);
             } catch (IOException e) {
-                Log.d(TAG, "Couldn't get OBB info", e);
+                Log.d(TAG, "Couldn't get OBB info for " + filename);
                 return null;
             }
         }
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index cfad939..2c13069 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -13,6 +13,6 @@
 LOCAL_PACKAGE_NAME := SystemUI
 LOCAL_CERTIFICATE := platform
 
-LOCAL_PROGUARD_FLAGS := -include $(LOCAL_PATH)/proguard.flags
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
 include $(BUILD_PACKAGE)
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.png
index c299e12..ae90cc8 100755
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_no_sim.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_no_sim.png
index 157491e..a0e59cf 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_no_sim.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_no_sim.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png
index 3e317dd..2f66b1d 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1.png
index aea18ed..1626895 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1_fully.png
old mode 100755
new mode 100644
index 1a25a2c..3c2e2b9
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_no_sim.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_no_sim.png
index 2134d49..bb41db0 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_no_sim.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_no_sim.png
Binary files differ
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java
index bf24a1f..16a3c17 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java
@@ -366,6 +366,7 @@
             mCarouselView.setStartAngle((float) -(2.0f*Math.PI * 5 / CARD_SLOTS));
             mCarouselView.setDefaultBitmap(mLoadingBitmap);
             mCarouselView.setLoadingBitmap(mLoadingBitmap);
+            mCarouselView.setRezInCardCount(3.0f);
             mCarouselView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
 
             mNoRecentsView = (View) findViewById(R.id.no_applications_message);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java
index 70d4d6a..d4491d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java
@@ -20,6 +20,8 @@
 import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.util.Slog;
+import android.view.View;
 import android.widget.ImageView;
 import android.widget.RemoteViews.RemoteView;
 
@@ -43,7 +45,7 @@
         }
         if (drawable instanceof AnimationDrawable) {
             mAnim = (AnimationDrawable)drawable;
-            if (mAttached) {
+            if (isShown()) {
                 mAnim.start();
             }
         } else {
@@ -67,9 +69,6 @@
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
-        if (mAnim != null) {
-            mAnim.start();
-        }
         mAttached = true;
     }
 
@@ -81,5 +80,17 @@
         }
         mAttached = false;
     }
+
+    @Override
+    protected void onVisibilityChanged(View changedView, int vis) {
+        super.onVisibilityChanged(changedView, vis);
+        if (mAnim != null) {
+            if (isShown()) {
+                mAnim.start();
+            } else {
+                mAnim.stop();
+            }
+        }
+    }
 }
 
diff --git a/packages/TtsService/Android.mk b/packages/TtsService/Android.mk
index 75b26a2..a1a3b9f 100644
--- a/packages/TtsService/Android.mk
+++ b/packages/TtsService/Android.mk
@@ -8,7 +8,7 @@
 LOCAL_PACKAGE_NAME := TtsService
 LOCAL_CERTIFICATE := platform
 
-LOCAL_PROGUARD_FLAGS := -include $(LOCAL_PATH)/proguard.flags
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
 include $(BUILD_PACKAGE)
 
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index c047522..bdf24d6 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -89,6 +89,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_DRAG;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
@@ -152,8 +153,11 @@
     // responsible for power management when displayed.
     static final int KEYGUARD_LAYER = 14;
     static final int KEYGUARD_DIALOG_LAYER = 15;
+    // the drag layer: input for drag-and-drop is associated with this window,
+    // which sits above all other focusable windows
+    static final int DRAG_LAYER = 16;
     // things in here CAN NOT take focus, but are shown on top of everything else.
-    static final int SYSTEM_OVERLAY_LAYER = 16;
+    static final int SYSTEM_OVERLAY_LAYER = 17;
 
     static final int APPLICATION_MEDIA_SUBLAYER = -2;
     static final int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1;
@@ -839,6 +843,8 @@
             return TOAST_LAYER;
         case TYPE_WALLPAPER:
             return WALLPAPER_LAYER;
+        case TYPE_DRAG:
+            return DRAG_LAYER;
         }
         Log.e(TAG, "Unknown window type: " + type);
         return APPLICATION_LAYER;
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 97b8086..8527059 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -1856,6 +1856,8 @@
                 if (--(track->mRetryCount) <= 0) {
                     LOGV("BUFFER TIMEOUT: remove(%d) from active list on thread %p", track->name(), this);
                     tracksToRemove->add(track);
+                    // indicate to client process that the track was disabled because of underrun
+                    cblk->flags |= CBLK_DISABLED_ON;
                 } else if (mixerStatus != MIXER_TRACKS_READY) {
                     mixerStatus = MIXER_TRACKS_ENABLED;
                 }
@@ -2790,7 +2792,7 @@
                     mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);
                     memset(mBuffer, 0, frameCount*channelCount*sizeof(int16_t));
                     // Force underrun condition to avoid false underrun callback until first data is
-                    // written to buffer
+                    // written to buffer (other flags are cleared)
                     mCblk->flags = CBLK_UNDERRUN_ON;
                 } else {
                     mBuffer = sharedBuffer->pointer();
@@ -2813,7 +2815,7 @@
            mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);
            memset(mBuffer, 0, frameCount*channelCount*sizeof(int16_t));
            // Force underrun condition to avoid false underrun callback until first data is
-           // written to buffer
+           // written to buffer (other flags are cleared)
            mCblk->flags = CBLK_UNDERRUN_ON;
            mBufferEnd = (uint8_t *)mBuffer + bufferSize;
        }
@@ -3794,6 +3796,8 @@
     AudioBufferProvider::Buffer buffer;
     sp<RecordTrack> activeTrack;
 
+    nsecs_t lastWarning = 0;
+
     // start recording
     while (!exitPending()) {
 
@@ -3935,8 +3939,13 @@
             }
             // client isn't retrieving buffers fast enough
             else {
-                if (!mActiveTrack->setOverflow())
-                    LOGW("RecordThread: buffer overflow");
+                if (!mActiveTrack->setOverflow()) {
+                    nsecs_t now = systemTime();
+                    if ((now - lastWarning) > kWarningThrottle) {
+                        LOGW("RecordThread: buffer overflow");
+                        lastWarning = now;
+                    }
+                }
                 // Release the processor for a while before asking for a new buffer.
                 // This will give the application more chance to read from the buffer and
                 // clear the overflow.
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 6095117..880befd 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -68,7 +68,7 @@
  */
 public class ConnectivityService extends IConnectivityManager.Stub {
 
-    private static final boolean DBG = true;
+    private static final boolean DBG = false;
     private static final String TAG = "ConnectivityService";
 
     // how long to wait before switching back to a radio's default network
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index 0c3a0e6..28126b9 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -58,6 +58,7 @@
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -605,6 +606,8 @@
             Slog.w(TAG, "failed parsing " + file + " " + e);
         } catch (XmlPullParserException e) {
             Slog.w(TAG, "failed parsing " + file + " " + e);
+        } catch (FileNotFoundException e) {
+            // Don't be noisy, this is normal if we haven't defined any policies.
         } catch (IOException e) {
             Slog.w(TAG, "failed parsing " + file + " " + e);
         } catch (IndexOutOfBoundsException e) {
diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java
index fe306b3..a960097 100644
--- a/services/java/com/android/server/InputManager.java
+++ b/services/java/com/android/server/InputManager.java
@@ -342,6 +342,7 @@
         if (toChannel == null) {
             throw new IllegalArgumentException("toChannel must not be null.");
         }
+        Slog.d(TAG, "transferring touch focus");
         return nativeTransferTouchFocus(fromChannel, toChannel);
     }
     
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 675760f..de28375 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -951,10 +951,11 @@
     }
 
     public void updateStatusIcon(IBinder token, String packageName, int iconId) {
+        int uid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
         try {
             if (token == null || mCurToken != token) {
-                Slog.w(TAG, "Ignoring setInputMethod of token: " + token);
+                Slog.w(TAG, "Ignoring setInputMethod of uid " + uid + " token: " + token);
                 return;
             }
 
@@ -1048,6 +1049,7 @@
 
     public boolean showSoftInput(IInputMethodClient client, int flags,
             ResultReceiver resultReceiver) {
+        int uid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mMethodMap) {
@@ -1058,7 +1060,7 @@
                         // focus in the window manager, to allow this call to
                         // be made before input is started in it.
                         if (!mIWindowManager.inputMethodClientHasFocus(client)) {
-                            Slog.w(TAG, "Ignoring showSoftInput of: " + client);
+                            Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
                             return false;
                         }
                     } catch (RemoteException e) {
@@ -1112,6 +1114,7 @@
 
     public boolean hideSoftInput(IInputMethodClient client, int flags,
             ResultReceiver resultReceiver) {
+        int uid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mMethodMap) {
@@ -1122,7 +1125,8 @@
                         // focus in the window manager, to allow this call to
                         // be made before input is started in it.
                         if (!mIWindowManager.inputMethodClientHasFocus(client)) {
-                            Slog.w(TAG, "Ignoring hideSoftInput of: " + client);
+                            if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
+                                    + uid + ": " + client);
                             return false;
                         }
                     } catch (RemoteException e) {
@@ -1257,7 +1261,8 @@
         synchronized (mMethodMap) {
             if (mCurClient == null || client == null
                     || mCurClient.client.asBinder() != client.asBinder()) {
-                Slog.w(TAG, "Ignoring showInputMethodDialogFromClient of: " + client);
+                Slog.w(TAG, "Ignoring showInputMethodDialogFromClient of uid "
+                        + Binder.getCallingUid() + ": " + client);
             }
 
             mHandler.sendEmptyMessage(MSG_SHOW_IM_PICKER);
@@ -1290,7 +1295,8 @@
                             + android.Manifest.permission.WRITE_SECURE_SETTINGS);
                 }
             } else if (mCurToken != token) {
-                Slog.w(TAG, "Ignoring setInputMethod of token: " + token);
+                Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
+                        + " token: " + token);
                 return;
             }
 
@@ -1306,7 +1312,8 @@
     public void hideMySoftInput(IBinder token, int flags) {
         synchronized (mMethodMap) {
             if (token == null || mCurToken != token) {
-                Slog.w(TAG, "Ignoring hideInputMethod of token: " + token);
+                if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
+                        + Binder.getCallingUid() + " token: " + token);
                 return;
             }
             long ident = Binder.clearCallingIdentity();
@@ -1321,7 +1328,8 @@
     public void showMySoftInput(IBinder token, int flags) {
         synchronized (mMethodMap) {
             if (token == null || mCurToken != token) {
-                Slog.w(TAG, "Ignoring hideInputMethod of token: " + token);
+                Slog.w(TAG, "Ignoring showMySoftInput of uid "
+                        + Binder.getCallingUid() + " token: " + token);
                 return;
             }
             long ident = Binder.clearCallingIdentity();
diff --git a/services/java/com/android/server/IntentResolver.java b/services/java/com/android/server/IntentResolver.java
index 8ab65e9..e47de13 100644
--- a/services/java/com/android/server/IntentResolver.java
+++ b/services/java/com/android/server/IntentResolver.java
@@ -28,6 +28,7 @@
 import java.util.Set;
 
 import android.util.Log;
+import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.LogPrinter;
 import android.util.Printer;
@@ -92,10 +93,12 @@
     }
 
     boolean dumpMap(PrintWriter out, String titlePrefix, String title,
-            String prefix, Map<String, ArrayList<F>> map, String packageName) {
+            String prefix, Map<String, ArrayList<F>> map, String packageName,
+            boolean printFilter) {
         String eprefix = prefix + "  ";
         String fprefix = prefix + "    ";
         boolean printedSomething = false;
+        Printer printer = null;
         for (Map.Entry<String, ArrayList<F>> e : map.entrySet()) {
             ArrayList<F> a = e.getValue();
             final int N = a.size();
@@ -115,37 +118,44 @@
                 }
                 printedSomething = true;
                 dumpFilter(out, fprefix, filter);
+                if (printFilter) {
+                    if (printer == null) {
+                        printer = new PrintWriterPrinter(out);
+                    }
+                    filter.dump(printer, fprefix + "  ");
+                }
             }
         }
         return printedSomething;
     }
 
-    public boolean dump(PrintWriter out, String title, String prefix, String packageName) {
+    public boolean dump(PrintWriter out, String title, String prefix, String packageName,
+            boolean printFilter) {
         String innerPrefix = prefix + "  ";
         String sepPrefix = "\n" + prefix;
         String curPrefix = title + "\n" + prefix;
         if (dumpMap(out, curPrefix, "Full MIME Types:", innerPrefix,
-                mTypeToFilter, packageName)) {
+                mTypeToFilter, packageName, printFilter)) {
             curPrefix = sepPrefix;
         }
         if (dumpMap(out, curPrefix, "Base MIME Types:", innerPrefix,
-                mBaseTypeToFilter, packageName)) {
+                mBaseTypeToFilter, packageName, printFilter)) {
             curPrefix = sepPrefix;
         }
         if (dumpMap(out, curPrefix, "Wild MIME Types:", innerPrefix,
-                mWildTypeToFilter, packageName)) {
+                mWildTypeToFilter, packageName, printFilter)) {
             curPrefix = sepPrefix;
         }
         if (dumpMap(out, curPrefix, "Schemes:", innerPrefix,
-                mSchemeToFilter, packageName)) {
+                mSchemeToFilter, packageName, printFilter)) {
             curPrefix = sepPrefix;
         }
         if (dumpMap(out, curPrefix, "Non-Data Actions:", innerPrefix,
-                mActionToFilter, packageName)) {
+                mActionToFilter, packageName, printFilter)) {
             curPrefix = sepPrefix;
         }
         if (dumpMap(out, curPrefix, "MIME Typed Actions:", innerPrefix,
-                mTypedActionToFilter, packageName)) {
+                mTypedActionToFilter, packageName, printFilter)) {
             curPrefix = sepPrefix;
         }
         return curPrefix == sepPrefix;
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index aa1bcf7..19ea4e1 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -58,6 +58,7 @@
 import android.util.Slog;
 import android.util.PrintWriterPrinter;
 
+import com.android.internal.content.PackageMonitor;
 import com.android.internal.location.GpsNetInitiatedHandler;
 
 import com.android.server.location.GeocoderProxy;
@@ -116,13 +117,15 @@
     private static boolean sProvidersLoaded = false;
 
     private final Context mContext;
+    private final String mNetworkLocationProviderPackageName;
+    private final String mGeocodeProviderPackageName;
     private GeocoderProxy mGeocodeProvider;
     private IGpsStatusProvider mGpsStatusProvider;
     private INetInitiatedListener mNetInitiatedListener;
     private LocationWorkerHandler mLocationHandler;
 
     // Cache the real providers for use in addTestProvider() and removeTestProvider()
-     LocationProviderInterface mNetworkLocationProvider;
+     LocationProviderProxy mNetworkLocationProvider;
      LocationProviderInterface mGpsLocationProvider;
 
     // Handler messages
@@ -472,20 +475,18 @@
         mEnabledProviders.add(passiveProvider.getName());
 
         // initialize external network location and geocoder services
-        PackageManager pm = mContext. getPackageManager();
-        Resources resources = mContext.getResources();
-        String serviceName = resources.getString(
-                com.android.internal.R.string.config_networkLocationProvider);
-        if (serviceName != null && pm.resolveService(new Intent(serviceName), 0) != null) {
+        PackageManager pm = mContext.getPackageManager();
+        if (mNetworkLocationProviderPackageName != null &&
+                pm.resolveService(new Intent(mNetworkLocationProviderPackageName), 0) != null) {
             mNetworkLocationProvider =
                 new LocationProviderProxy(mContext, LocationManager.NETWORK_PROVIDER,
-                        serviceName, mLocationHandler);
+                        mNetworkLocationProviderPackageName, mLocationHandler);
             addProvider(mNetworkLocationProvider);
         }
 
-        serviceName = resources.getString(com.android.internal.R.string.config_geocodeProvider);
-        if (serviceName != null && pm.resolveService(new Intent(serviceName), 0) != null) {
-            mGeocodeProvider = new GeocoderProxy(mContext, serviceName);
+        if (mGeocodeProviderPackageName != null &&
+                pm.resolveService(new Intent(mGeocodeProviderPackageName), 0) != null) {
+            mGeocodeProvider = new GeocoderProxy(mContext, mGeocodeProviderPackageName);
         }
 
         updateProvidersLocked();
@@ -497,6 +498,12 @@
     public LocationManagerService(Context context) {
         super();
         mContext = context;
+        Resources resources = context.getResources();
+        mNetworkLocationProviderPackageName = resources.getString(
+                com.android.internal.R.string.config_networkLocationProvider);
+        mGeocodeProviderPackageName = resources.getString(
+                com.android.internal.R.string.config_geocodeProvider);
+        mPackageMonitor.register(context, true);
 
         if (LOCAL_LOGV) {
             Slog.v(TAG, "Constructed LocationManager Service");
@@ -1921,6 +1928,23 @@
         }
     };
 
+    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+        @Override
+        public void onPackageUpdateFinished(String packageName, int uid) {
+            String packageDot = packageName + ".";
+
+            // reconnect to external providers after their packages have been updated
+            if (mNetworkLocationProvider != null &&
+                    mNetworkLocationProviderPackageName.startsWith(packageDot)) {
+                mNetworkLocationProvider.reconnect();
+            }
+            if (mGeocodeProvider != null &&
+                    mGeocodeProviderPackageName.startsWith(packageDot)) {
+                mGeocodeProvider.reconnect();
+            }
+        }
+    };
+
     // Wake locks
 
     private void incrementPendingBroadcasts() {
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index ca8fc52..d3b338c 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import com.android.internal.app.IMediaContainerService;
+import com.android.internal.util.HexDump;
 import com.android.server.am.ActivityManagerService;
 
 import android.content.BroadcastReceiver;
@@ -44,15 +45,22 @@
 import android.os.storage.IMountShutdownObserver;
 import android.os.storage.IObbActionListener;
 import android.os.storage.StorageResultCode;
+import android.security.MessageDigest;
 import android.util.Slog;
 
+import java.io.FileDescriptor;
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 
 /**
  * MountService implements back-end services for platform storage
@@ -71,6 +79,8 @@
 
     private static final String VOLD_TAG = "VoldConnector";
 
+    protected static final int MAX_OBBS = 8;
+
     /*
      * Internal vold volume state constants
      */
@@ -151,15 +161,19 @@
      * Mounted OBB tracking information. Used to track the current state of all
      * OBBs.
      */
-    final private Map<IObbActionListener, List<ObbState>> mObbMounts = new HashMap<IObbActionListener, List<ObbState>>();
+    final private Map<Integer, Integer> mObbUidUsage = new HashMap<Integer, Integer>();
+    final private Map<IBinder, List<ObbState>> mObbMounts = new HashMap<IBinder, List<ObbState>>();
     final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>();
 
     class ObbState implements IBinder.DeathRecipient {
-        public ObbState(String filename, IObbActionListener token, int callerUid) {
+        public ObbState(String filename, IObbActionListener token, int callerUid)
+                throws RemoteException {
             this.filename = filename;
             this.token = token;
             this.callerUid = callerUid;
             mounted = false;
+
+            getBinder().linkToDeath(this, 0);
         }
 
         // OBB source filename
@@ -174,14 +188,33 @@
         // Whether this is mounted currently.
         boolean mounted;
 
+        public IBinder getBinder() {
+            return token.asBinder();
+        }
+
         @Override
         public void binderDied() {
             ObbAction action = new UnmountObbAction(this, true);
             mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
+        }
 
-            removeObbState(this);
+        public void cleanUp() {
+            getBinder().unlinkToDeath(this, 0);
+        }
 
-            token.asBinder().unlinkToDeath(this, 0);
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder("ObbState{");
+            sb.append("filename=");
+            sb.append(filename);
+            sb.append(",token=");
+            sb.append(token.toString());
+            sb.append(",callerUid=");
+            sb.append(callerUid);
+            sb.append(",mounted=");
+            sb.append(mounted);
+            sb.append('}');
+            return sb.toString();
         }
     }
 
@@ -481,6 +514,34 @@
                 mPms.updateExternalMediaStatus(true, false);
             }
         }
+
+        // Remove all OBB mappings and listeners from this path
+        synchronized (mObbMounts) {
+            final List<ObbState> obbStatesToRemove = new LinkedList<ObbState>();
+
+            final Iterator<Entry<String, ObbState>> i = mObbPathToStateMap.entrySet().iterator();
+            while (i.hasNext()) {
+                final Entry<String, ObbState> obbEntry = i.next();
+
+                // If this entry's source file is in the volume path that got
+                // unmounted, remove it because it's no longer valid.
+                if (obbEntry.getKey().startsWith(path)) {
+                    obbStatesToRemove.add(obbEntry.getValue());
+                }
+            }
+
+            for (final ObbState obbState : obbStatesToRemove) {
+                removeObbState(obbState);
+
+                try {
+                    obbState.token.onObbResult(obbState.filename, Environment.MEDIA_UNMOUNTED);
+                } catch (RemoteException e) {
+                    Slog.i(TAG, "Couldn't send unmount notification for  OBB: "
+                            + obbState.filename);
+                }
+            }
+        }
+
         String oldState = mLegacyState;
         mLegacyState = state;
 
@@ -1507,11 +1568,18 @@
 
     public boolean isObbMounted(String filename) {
         synchronized (mObbMounts) {
-            return mObbPathToStateMap.containsKey(filename);
+            final ObbState obbState = mObbPathToStateMap.get(filename);
+            if (obbState != null) {
+                synchronized (obbState) {
+                    return obbState.mounted;
+                }
+            }
         }
+        return false;
     }
 
-    public void mountObb(String filename, String key, IObbActionListener token) {
+    public void mountObb(String filename, String key, IObbActionListener token)
+            throws RemoteException {
         waitForReady();
         warnOnNotMounted();
 
@@ -1525,21 +1593,44 @@
 
         synchronized (mObbMounts) {
             if (isObbMounted(filename)) {
-                throw new IllegalArgumentException("OBB file is already mounted");
+                try {
+                    token.onObbResult(filename, Environment.MEDIA_MOUNTED);
+                } catch (RemoteException e) {
+                    Slog.d(TAG, "Could not send unmount notification for: " + filename);
+                }
+                return;
             }
 
             final int callerUid = Binder.getCallingUid();
+
+            final Integer uidUsage = mObbUidUsage.get(callerUid);
+            if (uidUsage != null && uidUsage > MAX_OBBS) {
+                throw new IllegalStateException("Maximum number of OBBs mounted!");
+            }
+
             obbState = new ObbState(filename, token, callerUid);
             addObbState(obbState);
         }
 
-        try {
-            token.asBinder().linkToDeath(obbState, 0);
-        } catch (RemoteException rex) {
-            Slog.e(TAG, "Failed to link to listener death");
+        String hashedKey = null;
+        if (key != null) {
+            final MessageDigest md;
+            try {
+                md = MessageDigest.getInstance("MD5");
+            } catch (NoSuchAlgorithmException e) {
+                Slog.e(TAG, "Could not load MD5 algorithm", e);
+                try {
+                    token.onObbResult(filename, Environment.MEDIA_UNMOUNTED);
+                } catch (RemoteException e1) {
+                    Slog.d(TAG, "Could not send unmount notification for: " + filename);
+                }
+                return;
+            }
+
+            hashedKey = HexDump.toHexString(md.digest(key.getBytes()));
         }
 
-        MountObbAction action = new MountObbAction(obbState, key);
+        ObbAction action = new MountObbAction(obbState, hashedKey);
         mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
 
         if (DEBUG_OBB)
@@ -1557,18 +1648,24 @@
 
         synchronized (mObbMounts) {
             if (!isObbMounted(filename)) {
-                throw new IllegalArgumentException("OBB is not mounted");
+                try {
+                    token.onObbResult(filename, Environment.MEDIA_UNMOUNTED);
+                } catch (RemoteException e) {
+                    Slog.d(TAG, "Could not send unmount notification for: " + filename);
+                }
+                return;
             }
+
             obbState = mObbPathToStateMap.get(filename);
 
             if (Binder.getCallingUid() != obbState.callerUid) {
                 throw new SecurityException("caller UID does not match original mount caller UID");
-            } else if (!token.asBinder().equals(obbState.token.asBinder())) {
+            } else if (!token.asBinder().equals(obbState.getBinder())) {
                 throw new SecurityException("caller does not match original mount caller");
             }
         }
 
-        UnmountObbAction action = new UnmountObbAction(obbState, force);
+        ObbAction action = new UnmountObbAction(obbState, force);
         mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
 
         if (DEBUG_OBB)
@@ -1577,26 +1674,57 @@
 
     private void addObbState(ObbState obbState) {
         synchronized (mObbMounts) {
-            List<ObbState> obbStates = mObbMounts.get(obbState.token);
+            List<ObbState> obbStates = mObbMounts.get(obbState.getBinder());
             if (obbStates == null) {
                 obbStates = new ArrayList<ObbState>();
-                mObbMounts.put(obbState.token, obbStates);
+                mObbMounts.put(obbState.getBinder(), obbStates);
             }
             obbStates.add(obbState);
             mObbPathToStateMap.put(obbState.filename, obbState);
+
+            // Track the number of OBBs used by this UID.
+            final int uid = obbState.callerUid;
+            final Integer uidUsage = mObbUidUsage.get(uid);
+            if (uidUsage == null) {
+                mObbUidUsage.put(uid, 1);
+            } else {
+                mObbUidUsage.put(uid, uidUsage + 1);
+            }
         }
     }
 
     private void removeObbState(ObbState obbState) {
         synchronized (mObbMounts) {
-            final List<ObbState> obbStates = mObbMounts.get(obbState.token);
+            final List<ObbState> obbStates = mObbMounts.get(obbState.getBinder());
             if (obbStates != null) {
                 obbStates.remove(obbState);
             }
             if (obbStates == null || obbStates.isEmpty()) {
-                mObbMounts.remove(obbState.token);
+                mObbMounts.remove(obbState.getBinder());
+                obbState.cleanUp();
             }
             mObbPathToStateMap.remove(obbState.filename);
+
+            // Track the number of OBBs used by this UID.
+            final int uid = obbState.callerUid;
+            final Integer uidUsage = mObbUidUsage.get(uid);
+            if (uidUsage == null) {
+                Slog.e(TAG, "Called removeObbState for UID that isn't in map: " + uid);
+            } else {
+                final int newUsage = uidUsage - 1;
+                if (newUsage == 0) {
+                    mObbUidUsage.remove(uid);
+                } else {
+                    mObbUidUsage.put(uid, newUsage);
+                }
+            }
+        }
+    }
+
+    private void replaceObbState(ObbState oldObbState, ObbState newObbState) {
+        synchronized (mObbMounts) {
+            removeObbState(oldObbState);
+            addObbState(newObbState);
         }
     }
 
@@ -1627,20 +1755,16 @@
                             Slog.e(TAG, "Failed to bind to media container service");
                             action.handleError();
                             return;
-                        } else {
-                            // Once we bind to the service, the first
-                            // pending request will be processed.
-                            mActions.add(action);
-                        }
-                    } else {
-                        // Already bound to the service. Just make
-                        // sure we trigger off processing the first request.
-                        if (mActions.size() == 0) {
-                            mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
                         }
 
                         mActions.add(action);
+                        break;
                     }
+
+                    // Once we bind to the service, the first
+                    // pending request will be processed.
+                    mActions.add(action);
+                    mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
                     break;
                 }
                 case OBB_MCS_BOUND: {
@@ -1773,6 +1897,29 @@
 
         abstract void handleExecute() throws RemoteException, IOException;
         abstract void handleError();
+
+        protected ObbInfo getObbInfo() throws IOException {
+            ObbInfo obbInfo;
+            try {
+                obbInfo = mContainerService.getObbInfo(mObbState.filename);
+            } catch (RemoteException e) {
+                Slog.d(TAG, "Couldn't call DefaultContainerService to fetch OBB info for "
+                        + mObbState.filename);
+                obbInfo = null;
+            }
+            if (obbInfo == null) {
+                throw new IOException("Couldn't read OBB file: " + mObbState.filename);
+            }
+            return obbInfo;
+        }
+
+        protected void sendNewStatusOrIgnore(String filename, String status) {
+            try {
+                mObbState.token.onObbResult(filename, status);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
+            }
+        }
     }
 
     class MountObbAction extends ObbAction {
@@ -1783,56 +1930,89 @@
             mKey = key;
         }
 
-        public void handleExecute() throws RemoteException, IOException {
-            final ObbInfo obbInfo = mContainerService.getObbInfo(mObbState.filename);
-            if (obbInfo == null) {
-                throw new IOException("Couldn't read OBB file: " + mObbState.filename);
+        public void handleExecute() throws IOException, RemoteException {
+            final ObbInfo obbInfo = getObbInfo();
+
+            /*
+             * If someone tried to trick us with some weird characters, rectify
+             * it here.
+             */
+            if (!mObbState.filename.equals(obbInfo.filename)) {
+                if (DEBUG_OBB)
+                    Slog.i(TAG, "OBB filename " + mObbState.filename + " is actually "
+                            + obbInfo.filename);
+
+                synchronized (mObbMounts) {
+                    /*
+                     * If the real filename is already mounted, discard this
+                     * state and notify the caller that the OBB is already
+                     * mounted.
+                     */
+                    if (isObbMounted(obbInfo.filename)) {
+                        if (DEBUG_OBB)
+                            Slog.i(TAG, "OBB already mounted as " + obbInfo.filename);
+
+                        removeObbState(mObbState);
+                        sendNewStatusOrIgnore(obbInfo.filename, Environment.MEDIA_MOUNTED);
+                        return;
+                    }
+
+                    /*
+                     * It's not already mounted, so we have to replace the state
+                     * with the state containing the actual filename.
+                     */
+                    ObbState newObbState = new ObbState(obbInfo.filename, mObbState.token,
+                            mObbState.callerUid);
+                    replaceObbState(mObbState, newObbState);
+                    mObbState = newObbState;
+                }
             }
 
             if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mObbState.callerUid)) {
                 throw new IllegalArgumentException("Caller package does not match OBB file");
             }
 
-            if (mKey == null) {
-                mKey = "none";
-            }
-
-            int rc = StorageResultCode.OperationSucceeded;
-            String cmd = String.format("obb mount %s %s %d", mObbState.filename, mKey,
-                    mObbState.callerUid);
-            try {
-                mConnector.doCommand(cmd);
-            } catch (NativeDaemonConnectorException e) {
-                int code = e.getCode();
-                if (code != VoldResponseCode.OpFailedStorageBusy) {
-                    rc = StorageResultCode.OperationFailedInternalError;
+            boolean mounted = false;
+            int rc;
+            synchronized (mObbState) {
+                if (mObbState.mounted) {
+                    sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_MOUNTED);
+                    return;
                 }
-            }
 
-            if (rc == StorageResultCode.OperationSucceeded) {
+                rc = StorageResultCode.OperationSucceeded;
+                String cmd = String.format("obb mount %s %s %d", mObbState.filename, mKey,
+                        mObbState.callerUid);
                 try {
-                    mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_MOUNTED);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
+                    mConnector.doCommand(cmd);
+                } catch (NativeDaemonConnectorException e) {
+                    int code = e.getCode();
+                    if (code != VoldResponseCode.OpFailedStorageBusy) {
+                        rc = StorageResultCode.OperationFailedInternalError;
+                    }
                 }
+
+                if (rc == StorageResultCode.OperationSucceeded) {
+                    mObbState.mounted = mounted = true;
+                }
+            }
+
+            if (mounted) {
+                sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_MOUNTED);
             } else {
                 Slog.e(TAG, "Couldn't mount OBB file: " + rc);
 
                 // We didn't succeed, so remove this from the mount-set.
                 removeObbState(mObbState);
 
-                mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_BAD_REMOVAL);
+                sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_UNMOUNTED);
             }
         }
 
         public void handleError() {
             removeObbState(mObbState);
 
-            try {
-                mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_BAD_REMOVAL);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Couldn't send back OBB mount error for " + mObbState.filename);
-            }
+            sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_BAD_REMOVAL);
         }
 
         @Override
@@ -1858,51 +2038,69 @@
             mForceUnmount = force;
         }
 
-        public void handleExecute() throws RemoteException, IOException {
-            final ObbInfo obbInfo = mContainerService.getObbInfo(mObbState.filename);
-            if (obbInfo == null) {
-                throw new IOException("Couldn't read OBB file: " + mObbState.filename);
-            }
+        public void handleExecute() throws IOException {
+            final ObbInfo obbInfo = getObbInfo();
 
-            int rc = StorageResultCode.OperationSucceeded;
-            String cmd = String.format("obb unmount %s%s", mObbState.filename,
-                    (mForceUnmount ? " force" : ""));
-            try {
-                mConnector.doCommand(cmd);
-            } catch (NativeDaemonConnectorException e) {
-                int code = e.getCode();
-                if (code == VoldResponseCode.OpFailedStorageBusy) {
-                    rc = StorageResultCode.OperationFailedStorageBusy;
-                } else {
-                    rc = StorageResultCode.OperationFailedInternalError;
+            /*
+             * If someone tried to trick us with some weird characters, rectify
+             * it here.
+             */
+            synchronized (mObbMounts) {
+                if (!isObbMounted(obbInfo.filename)) {
+                    removeObbState(mObbState);
+                    sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_UNMOUNTED);
+                    return;
+                }
+
+                if (!mObbState.filename.equals(obbInfo.filename)) {
+                    removeObbState(mObbState);
+                    mObbState = mObbPathToStateMap.get(obbInfo.filename);
                 }
             }
 
-            if (rc == StorageResultCode.OperationSucceeded) {
+            boolean unmounted = false;
+            synchronized (mObbState) {
+                if (!mObbState.mounted) {
+                    sendNewStatusOrIgnore(obbInfo.filename, Environment.MEDIA_UNMOUNTED);
+                    return;
+                }
+
+                int rc = StorageResultCode.OperationSucceeded;
+                String cmd = String.format("obb unmount %s%s", mObbState.filename,
+                        (mForceUnmount ? " force" : ""));
+                try {
+                    mConnector.doCommand(cmd);
+                } catch (NativeDaemonConnectorException e) {
+                    int code = e.getCode();
+                    if (code == VoldResponseCode.OpFailedStorageBusy) {
+                        rc = StorageResultCode.OperationFailedStorageBusy;
+                    } else if (code == VoldResponseCode.OpFailedStorageNotFound) {
+                        // If it's not mounted then we've already won.
+                        rc = StorageResultCode.OperationSucceeded;
+                    } else {
+                        rc = StorageResultCode.OperationFailedInternalError;
+                    }
+                }
+
+                if (rc == StorageResultCode.OperationSucceeded) {
+                    mObbState.mounted = false;
+                    unmounted = true;
+                }
+            }
+
+            if (unmounted) {
                 removeObbState(mObbState);
 
-                try {
-                    mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_UNMOUNTED);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
-                }
+                sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_UNMOUNTED);
             } else {
-                try {
-                    mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_BAD_REMOVAL);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
-                }
+                sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_MOUNTED);
             }
         }
 
         public void handleError() {
             removeObbState(mObbState);
 
-            try {
-                mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_BAD_REMOVAL);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Couldn't send back OBB unmount error for " + mObbState.filename);
-            }
+            sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_BAD_REMOVAL);
         }
 
         @Override
@@ -1917,9 +2115,33 @@
             sb.append(mObbState.callerUid);
             sb.append(",token=");
             sb.append(mObbState.token != null ? mObbState.token.toString() : "null");
+            sb.append(",binder=");
+            sb.append(mObbState.getBinder().toString());
             sb.append('}');
             return sb.toString();
         }
     }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump ActivityManager from from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                    + " without permission " + android.Manifest.permission.DUMP);
+            return;
+        }
+
+        pw.println("  mObbMounts:");
+
+        synchronized (mObbMounts) {
+            final Collection<List<ObbState>> obbStateLists = mObbMounts.values();
+
+            for (final List<ObbState> obbStates : obbStateLists) {
+                for (final ObbState obbState : obbStates) {
+                    pw.print("    "); pw.println(obbState.toString());
+                }
+            }
+        }
+    }
 }
 
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 05abd99..8dbd3e7 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -55,7 +55,7 @@
 class NetworkManagementService extends INetworkManagementService.Stub {
 
     private static final String TAG = "NetworkManagmentService";
-    private static final boolean DBG = true;
+    private static final boolean DBG = false;
     private static final String NETD_TAG = "NetdConnector";
 
     class NetdResponseCode {
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 8f90756..1f97bee 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -1102,27 +1102,6 @@
         final File permFile = new File(Environment.getRootDirectory(),
                 "etc/permissions/platform.xml");
         readPermissionsFromXml(permFile);
-
-        StringBuilder sb = new StringBuilder(128);
-        sb.append("Libs:");
-        Iterator<String> it = mSharedLibraries.keySet().iterator();
-        while (it.hasNext()) {
-            sb.append(' ');
-            String name = it.next();
-            sb.append(name);
-            sb.append(':');
-            sb.append(mSharedLibraries.get(name));
-        }
-        Log.i(TAG, sb.toString());
-
-        sb.setLength(0);
-        sb.append("Features:");
-        it = mAvailableFeatures.keySet().iterator();
-        while (it.hasNext()) {
-            sb.append(' ');
-            sb.append(it.next());
-        }
-        Log.i(TAG, sb.toString());
     }
 
     private void readPermissionsFromXml(File permFile) {
@@ -2632,7 +2611,7 @@
                     // The system package has been updated and the code path does not match
                     // Ignore entry. Skip it.
                     Log.i(TAG, "Package " + ps.name + " at " + scanFile
-                            + "ignored: updated version " + ps.versionCode
+                            + " ignored: updated version " + ps.versionCode
                             + " better than this " + pkg.mVersionCode);
                     mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
                     return null;
@@ -6866,7 +6845,18 @@
             return;
         }
 
+        boolean dumpStar = true;
+        boolean dumpLibs = false;
+        boolean dumpFeatures = false;
+        boolean dumpResolvers = false;
+        boolean dumpPermissions = false;
+        boolean dumpPackages = false;
+        boolean dumpSharedUsers = false;
+        boolean dumpMessages = false;
+        boolean dumpProviders = false;
+        
         String packageName = null;
+        boolean showFilters = false;
         
         int opti = 0;
         while (opti < args.length) {
@@ -6879,10 +6869,22 @@
                 // Right now we only know how to print all.
             } else if ("-h".equals(opt)) {
                 pw.println("Package manager dump options:");
-                pw.println("  [-h] [cmd] ...");
+                pw.println("  [-h] [-f] [cmd] ...");
+                pw.println("    -f: print details of intent filters");
+                pw.println("    -h: print this help");
                 pw.println("  cmd may be one of:");
-                pw.println("    [package.name]: info about given package");
+                pw.println("    l[ibraries]: list known shared libraries");
+                pw.println("    f[ibraries]: list device features");
+                pw.println("    r[esolvers]: dump intent resolvers");
+                pw.println("    perm[issions]: dump permissions");
+                pw.println("    prov[iders]: dump content providers");
+                pw.println("    p[ackages]: dump installed packages");
+                pw.println("    s[hared-users]: dump shared user IDs");
+                pw.println("    m[essages]: print collected runtime messages");
+                pw.println("    <package.name>: info about given package");
                 return;
+            } else if ("-f".equals(opt)) {
+                showFilters = true;
             } else {
                 pw.println("Unknown argument: " + opt + "; use -h for help");
             }
@@ -6895,32 +6897,87 @@
             // Is this a package name?
             if ("android".equals(cmd) || cmd.contains(".")) {
                 packageName = cmd;
+            } else if ("l".equals(cmd) || "libraries".equals(cmd)) {
+                dumpStar = false;
+                dumpLibs = true;
+            } else if ("f".equals(cmd) || "features".equals(cmd)) {
+                dumpStar = false;
+                dumpFeatures = true;
+            } else if ("r".equals(cmd) || "resolvers".equals(cmd)) {
+                dumpStar = false;
+                dumpResolvers = true;
+            } else if ("perm".equals(cmd) || "permissions".equals(cmd)) {
+                dumpStar = false;
+                dumpPermissions = true;
+            } else if ("p".equals(cmd) || "packages".equals(cmd)) {
+                dumpStar = false;
+                dumpPackages = true;
+            } else if ("s".equals(cmd) || "shared-users".equals(cmd)) {
+                dumpStar = false;
+                dumpSharedUsers = true;
+            } else if ("prov".equals(cmd) || "providers".equals(cmd)) {
+                dumpStar = false;
+                dumpProviders = true;
+            } else if ("m".equals(cmd) || "messages".equals(cmd)) {
+                dumpStar = false;
+                dumpMessages = true;
             }
         }
         
         boolean printedTitle = false;
         
         synchronized (mPackages) {
-            if (mActivities.dump(pw, "Activity Resolver Table:", "  ", packageName)) {
+            if ((dumpStar || dumpLibs) && packageName == null) {
+                if (printedTitle) pw.println(" ");
                 printedTitle = true;
+                pw.println("Libraries:");
+                Iterator<String> it = mSharedLibraries.keySet().iterator();
+                while (it.hasNext()) {
+                    String name = it.next();
+                    pw.print("  ");
+                    pw.print(name);
+                    pw.print(" -> ");
+                    pw.println(mSharedLibraries.get(name));
+                }
             }
-            if (mReceivers.dump(pw, printedTitle
-                    ? "\nReceiver Resolver Table:" : "Receiver Resolver Table:",
-                    "  ", packageName)) {
+
+            if ((dumpStar || dumpFeatures) && packageName == null) {
+                if (printedTitle) pw.println(" ");
                 printedTitle = true;
+                pw.println("Features:");
+                Iterator<String> it = mAvailableFeatures.keySet().iterator();
+                while (it.hasNext()) {
+                    String name = it.next();
+                    pw.print("  ");
+                    pw.println(name);
+                }
             }
-            if (mServices.dump(pw, printedTitle
-                    ? "\nService Resolver Table:" : "Service Resolver Table:",
-                    "  ", packageName)) {
-                printedTitle = true;
+
+            if (dumpStar || dumpResolvers) {
+                if (mActivities.dump(pw, printedTitle
+                        ? "\nActivity Resolver Table:" : "Activity Resolver Table:",
+                        "  ", packageName, showFilters)) {
+                    printedTitle = true;
+                }
+                if (mReceivers.dump(pw, printedTitle
+                        ? "\nReceiver Resolver Table:" : "Receiver Resolver Table:",
+                        "  ", packageName, showFilters)) {
+                    printedTitle = true;
+                }
+                if (mServices.dump(pw, printedTitle
+                        ? "\nService Resolver Table:" : "Service Resolver Table:",
+                        "  ", packageName, showFilters)) {
+                    printedTitle = true;
+                }
+                if (mSettings.mPreferredActivities.dump(pw, printedTitle
+                        ? "\nPreferred Activities:" : "Preferred Activities:",
+                        "  ", packageName, showFilters)) {
+                    printedTitle = true;
+                }
             }
-            if (mSettings.mPreferredActivities.dump(pw, printedTitle
-                    ? "\nPreferred Activities:" : "Preferred Activities:",
-                    "  ", packageName)) {
-                printedTitle = true;
-            }
+            
             boolean printedSomething = false;
-            {
+            if (dumpStar || dumpPermissions) {
                 for (BasePermission p : mSettings.mPermissions.values()) {
                     if (packageName != null && !packageName.equals(p.sourcePackage)) {
                         continue;
@@ -6947,9 +7004,27 @@
                     }
                 }
             }
+
+            if (dumpStar || dumpProviders) {
+                printedSomething = false;
+                for (PackageParser.Provider p : mProviders.values()) {
+                    if (packageName != null && !packageName.equals(p.info.packageName)) {
+                        continue;
+                    }
+                    if (!printedSomething) {
+                        if (printedTitle) pw.println(" ");
+                        pw.println("Registered ContentProviders:");
+                        printedSomething = true;
+                        printedTitle = true;
+                    }
+                    pw.print("  ["); pw.print(p.info.authority); pw.print("]: ");
+                            pw.println(p.toString());
+                }
+            }
+            
             printedSomething = false;
             SharedUserSetting packageSharedUser = null;
-            {
+            if (dumpStar || dumpPackages) {
                 for (PackageSetting ps : mSettings.mPackages.values()) {
                     if (packageName != null && !packageName.equals(ps.realName)
                             && !packageName.equals(ps.name)) {
@@ -7052,52 +7127,54 @@
                 }
             }
             printedSomething = false;
-            if (mSettings.mRenamedPackages.size() > 0) {
-                for (HashMap.Entry<String, String> e
-                        : mSettings.mRenamedPackages.entrySet()) {
-                    if (packageName != null && !packageName.equals(e.getKey())
-                            && !packageName.equals(e.getValue())) {
-                        continue;
+            if (dumpStar || dumpPackages) {
+                if (mSettings.mRenamedPackages.size() > 0) {
+                    for (HashMap.Entry<String, String> e
+                            : mSettings.mRenamedPackages.entrySet()) {
+                        if (packageName != null && !packageName.equals(e.getKey())
+                                && !packageName.equals(e.getValue())) {
+                            continue;
+                        }
+                        if (!printedSomething) {
+                            if (printedTitle) pw.println(" ");
+                            pw.println("Renamed packages:");
+                            printedSomething = true;
+                            printedTitle = true;
+                        }
+                        pw.print("  "); pw.print(e.getKey()); pw.print(" -> ");
+                                pw.println(e.getValue());
                     }
-                    if (!printedSomething) {
-                        if (printedTitle) pw.println(" ");
-                        pw.println("Renamed packages:");
-                        printedSomething = true;
-                        printedTitle = true;
+                }
+                printedSomething = false;
+                if (mSettings.mDisabledSysPackages.size() > 0) {
+                    for (PackageSetting ps : mSettings.mDisabledSysPackages.values()) {
+                        if (packageName != null && !packageName.equals(ps.realName)
+                                && !packageName.equals(ps.name)) {
+                            continue;
+                        }
+                        if (!printedSomething) {
+                            if (printedTitle) pw.println(" ");
+                            pw.println("Hidden system packages:");
+                            printedSomething = true;
+                            printedTitle = true;
+                        }
+                       pw.print("  Package [");
+                                pw.print(ps.realName != null ? ps.realName : ps.name);
+                                pw.print("] (");
+                                pw.print(Integer.toHexString(System.identityHashCode(ps)));
+                                pw.println("):");
+                        if (ps.realName != null) {
+                            pw.print("    compat name="); pw.println(ps.name);
+                        }
+                        pw.print("    userId="); pw.println(ps.userId);
+                        pw.print("    sharedUser="); pw.println(ps.sharedUser);
+                        pw.print("    codePath="); pw.println(ps.codePathString);
+                        pw.print("    resourcePath="); pw.println(ps.resourcePathString);
                     }
-                    pw.print("  "); pw.print(e.getKey()); pw.print(" -> ");
-                            pw.println(e.getValue());
                 }
             }
             printedSomething = false;
-            if (mSettings.mDisabledSysPackages.size() > 0) {
-                for (PackageSetting ps : mSettings.mDisabledSysPackages.values()) {
-                    if (packageName != null && !packageName.equals(ps.realName)
-                            && !packageName.equals(ps.name)) {
-                        continue;
-                    }
-                    if (!printedSomething) {
-                        if (printedTitle) pw.println(" ");
-                        pw.println("Hidden system packages:");
-                        printedSomething = true;
-                        printedTitle = true;
-                    }
-                   pw.print("  Package [");
-                            pw.print(ps.realName != null ? ps.realName : ps.name);
-                            pw.print("] (");
-                            pw.print(Integer.toHexString(System.identityHashCode(ps)));
-                            pw.println("):");
-                    if (ps.realName != null) {
-                        pw.print("    compat name="); pw.println(ps.name);
-                    }
-                    pw.print("    userId="); pw.println(ps.userId);
-                    pw.print("    sharedUser="); pw.println(ps.sharedUser);
-                    pw.print("    codePath="); pw.println(ps.codePathString);
-                    pw.print("    resourcePath="); pw.println(ps.resourcePathString);
-                }
-            }
-            printedSomething = false;
-            {
+            if (dumpStar || dumpSharedUsers) {
                 for (SharedUserSetting su : mSettings.mSharedUsers.values()) {
                     if (packageName != null && su != packageSharedUser) {
                         continue;
@@ -7120,11 +7197,11 @@
                 }
             }
             
-            if (packageName == null) {
+            if ((dumpStar || dumpMessages) && packageName == null) {
                 if (printedTitle) pw.println(" ");
                 printedTitle = true;
                 pw.println("Settings parse messages:");
-                pw.println(mSettings.mReadMessages.toString());
+                pw.print(mSettings.mReadMessages.toString());
                 
                 pw.println(" ");
                 pw.println("Package warning messages:");
@@ -7135,29 +7212,12 @@
                     int avail = in.available();
                     byte[] data = new byte[avail];
                     in.read(data);
-                    pw.println(new String(data));
+                    pw.print(new String(data));
                 } catch (FileNotFoundException e) {
                 } catch (IOException e) {
                 }
             }
         }
-
-        synchronized (mProviders) {
-            boolean printedSomething = false;
-            for (PackageParser.Provider p : mProviders.values()) {
-                if (packageName != null && !packageName.equals(p.info.packageName)) {
-                    continue;
-                }
-                if (!printedSomething) {
-                    if (printedTitle) pw.println(" ");
-                    pw.println("Registered ContentProviders:");
-                    printedSomething = true;
-                    printedTitle = true;
-                }
-                pw.print("  ["); pw.print(p.info.authority); pw.print("]: ");
-                        pw.println(p.toString());
-            }
-        }
     }
 
     static final class BasePermission {
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index 4532c1c..29a9a7e 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -871,7 +871,7 @@
             mWakeLockState = mLocks.gatherState();
             // goes in the middle to reduce flicker
             if ((wl.flags & PowerManager.ON_AFTER_RELEASE) != 0) {
-                userActivity(SystemClock.uptimeMillis(), false);
+                userActivity(SystemClock.uptimeMillis(), -1, false, OTHER_EVENT, false);
             }
             setPowerState(mWakeLockState | mUserState);
         }
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 7100cc5..0c1417d 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -40,6 +40,7 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.policy.PolicyManager;
 import com.android.internal.policy.impl.PhoneWindowManager;
+import com.android.internal.view.BaseInputHandler;
 import com.android.internal.view.IInputContext;
 import com.android.internal.view.IInputMethodClient;
 import com.android.internal.view.IInputMethodManager;
@@ -51,6 +52,8 @@
 import android.app.IActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -93,6 +96,7 @@
 import android.util.SparseIntArray;
 import android.util.TypedValue;
 import android.view.Display;
+import android.view.DragEvent;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.IApplicationToken;
@@ -104,6 +108,8 @@
 import android.view.InputChannel;
 import android.view.InputDevice;
 import android.view.InputEvent;
+import android.view.InputHandler;
+import android.view.InputQueue;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -159,6 +165,7 @@
     static final boolean DEBUG_STARTING_WINDOW = false;
     static final boolean DEBUG_REORDER = false;
     static final boolean DEBUG_WALLPAPER = false;
+    static final boolean DEBUG_DRAG = true;
     static final boolean SHOW_TRANSACTIONS = false;
     static final boolean HIDE_STACK_CRAWLS = true;
 
@@ -486,6 +493,311 @@
     boolean mTurnOnScreen;
 
     /**
+     * Drag/drop state
+     */
+    class DragState {
+        IBinder mToken;
+        Surface mSurface;
+        boolean mLocalOnly;
+        ClipData mData;
+        ClipDescription mDataDescription;
+        float mThumbOffsetX, mThumbOffsetY;
+        InputChannel mServerChannel, mClientChannel;
+        WindowState mTargetWindow;
+        ArrayList<WindowState> mNotifiedWindows;
+        boolean mDragEnded;
+
+        private final Rect tmpRect = new Rect();
+
+        DragState(IBinder token, Surface surface, boolean localOnly) {
+            mToken = token;
+            mSurface = surface;
+            mLocalOnly = localOnly;
+            mNotifiedWindows = new ArrayList<WindowState>();
+        }
+
+        void reset() {
+            if (mSurface != null) {
+                mSurface.destroy();
+            }
+            mSurface = null;
+            mLocalOnly = false;
+            mToken = null;
+            mData = null;
+            mThumbOffsetX = mThumbOffsetY = 0;
+            mNotifiedWindows = null;
+        }
+
+        void register() {
+            if (DEBUG_DRAG) Slog.d(TAG, "registering drag input channel");
+            if (mClientChannel != null) {
+                Slog.e(TAG, "Duplicate register of drag input channel");
+            } else {
+                InputChannel[] channels = InputChannel.openInputChannelPair("drag");
+                mServerChannel = channels[0];
+                mClientChannel = channels[1];
+                mInputManager.registerInputChannel(mServerChannel);
+                InputQueue.registerInputChannel(mClientChannel, mDragInputHandler,
+                        mH.getLooper().getQueue());
+            }
+        }
+
+        void unregister() {
+            if (DEBUG_DRAG) Slog.d(TAG, "unregistering drag input channel");
+            if (mClientChannel == null) {
+                Slog.e(TAG, "Unregister of nonexistent drag input channel");
+            } else {
+                mInputManager.unregisterInputChannel(mServerChannel);
+                InputQueue.unregisterInputChannel(mClientChannel);
+                mClientChannel.dispose();
+                mClientChannel = null;
+                mServerChannel = null;
+            }
+        }
+
+        /* call out to each visible window/session informing it about the drag
+         */
+        void broadcastDragStartedLw() {
+            // Cache a base-class instance of the clip metadata so that parceling
+            // works correctly in calling out to the apps.
+            mDataDescription = new ClipDescription(mData);
+            mNotifiedWindows.clear();
+
+            if (DEBUG_DRAG) {
+                Slog.d(TAG, "broadcasting DRAG_STARTED of " + mDataDescription);
+            }
+
+            DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_STARTED, 0, 0,
+                    mDataDescription, null);
+            for (WindowState ws : mWindows) {
+                sendDragStartedLw(ws, evt);
+            }
+            evt.recycle();
+        }
+
+        /* helper - send a caller-provided event, presumed to be DRAG_STARTED, if the
+         * designated window is potentially a drop recipient.  There are race situations
+         * around DRAG_ENDED broadcast, so we make sure that once we've declared that
+         * the drag has ended, we never send out another DRAG_STARTED for this drag action.
+         */
+        private void sendDragStartedLw(WindowState newWin, final DragEvent event) {
+            if (!mDragEnded && newWin.isPotentialDragTarget()) {
+                try {
+                    newWin.mClient.dispatchDragEvent(event);
+                    // track each window that we've notified that the drag is starting
+                    mNotifiedWindows.add(newWin);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Unable to drag-start window " + newWin);
+                }
+            }
+        }
+
+        /* helper - construct and send a DRAG_STARTED event only if the window has not
+         * previously been notified, i.e. it became visible after the drag operation
+         * was begun.  This is a rare case.
+         */
+        private void sendDragStartedIfNeededLw(WindowState newWin) {
+            // If we have sent the drag-started, we needn't do so again
+            for (WindowState ws : mNotifiedWindows) {
+                if (ws == newWin) {
+                    return;
+                }
+            }
+            if (DEBUG_DRAG) {
+                Slog.d(TAG, "sending DRAG_STARTED to new window " + newWin);
+            }
+            DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_STARTED, 0, 0,
+                    mDataDescription, null);
+            sendDragStartedLw(newWin, event);
+            event.recycle();
+        }
+
+        void broadcastDragEnded() {
+            if (DEBUG_DRAG) {
+                Slog.d(TAG, "broadcasting DRAG_ENDED");
+            }
+            DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, 0, 0, null, null);
+            synchronized (mWindowMap) {
+                for (WindowState ws: mNotifiedWindows) {
+                    try {
+                        ws.mClient.dispatchDragEvent(evt);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Unable to drag-end window " + ws);
+                    }
+                }
+                mNotifiedWindows.clear();
+                mDragEnded = true;
+            }
+            evt.recycle();
+        }
+
+        void notifyMoveLw(float x, float y) {
+            WindowState touchedWin = getTouchedWinAtPointLw(x, y);
+            try {
+                // have we dragged over a new window?
+                if ((touchedWin != mTargetWindow) && (mTargetWindow != null)) {
+                    if (DEBUG_DRAG) {
+                        Slog.d(TAG, "sending DRAG_EXITED to " + mTargetWindow);
+                    }
+                    // force DRAG_EXITED_EVENT if appropriate
+                    DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_EXITED,
+                            0, 0, null, null);
+                    mTargetWindow.mClient.dispatchDragEvent(evt);
+                    evt.recycle();
+                }
+                if (touchedWin != null) {
+                    if (DEBUG_DRAG) {
+                        Slog.d(TAG, "sending DRAG_LOCATION to " + touchedWin);
+                    }
+                    DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_LOCATION,
+                            x, y, null, null);
+                    touchedWin.mClient.dispatchDragEvent(evt);
+                    evt.recycle();
+                }
+            } catch (RemoteException e) {
+                Slog.w(TAG, "can't send drag notification to windows");
+            }
+            mTargetWindow = touchedWin;
+        }
+
+        // Tell the drop target about the data, and then broadcast the drag-ended notification
+        void notifyDropLw(float x, float y) {
+            WindowState touchedWin = getTouchedWinAtPointLw(x, y);
+            if (touchedWin != null) {
+                if (DEBUG_DRAG) {
+                    Slog.d(TAG, "sending DROP to " + touchedWin);
+                }
+                DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DROP, x, y, null, mData);
+                try {
+                    touchedWin.mClient.dispatchDragEvent(evt);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "can't send drop notification to win " + touchedWin);
+                }
+                evt.recycle();
+            }
+        }
+
+        // Find the visible, touch-deliverable window under the given point
+        private WindowState getTouchedWinAtPointLw(float xf, float yf) {
+            WindowState touchedWin = null;
+            final int x = (int) xf;
+            final int y = (int) yf;
+            final ArrayList<WindowState> windows = mWindows;
+            final int N = windows.size();
+            for (int i = N - 1; i >= 0; i--) {
+                WindowState child = windows.get(i);
+                final int flags = child.mAttrs.flags;
+                if (!child.isVisibleLw()) {
+                    // not visible == don't tell about drags
+                    continue;
+                }
+                if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
+                    // not touchable == don't tell about drags
+                    continue;
+                }
+                // account for the window's decor etc
+                tmpRect.set(child.mFrame);
+                if (child.mTouchableInsets == ViewTreeObserver
+                            .InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT) {
+                    // The point is inside of the window if it is
+                    // inside the frame, AND the content part of that
+                    // frame that was given by the application.
+                    tmpRect.left += child.mGivenContentInsets.left;
+                    tmpRect.top += child.mGivenContentInsets.top;
+                    tmpRect.right -= child.mGivenContentInsets.right;
+                    tmpRect.bottom -= child.mGivenContentInsets.bottom;
+                } else if (child.mTouchableInsets == ViewTreeObserver
+                            .InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE) {
+                    // The point is inside of the window if it is
+                    // inside the frame, AND the visible part of that
+                    // frame that was given by the application.
+                    tmpRect.left += child.mGivenVisibleInsets.left;
+                    tmpRect.top += child.mGivenVisibleInsets.top;
+                    tmpRect.right -= child.mGivenVisibleInsets.right;
+                    tmpRect.bottom -= child.mGivenVisibleInsets.bottom;
+                }
+                final int touchFlags = flags &
+                    (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
+                if (tmpRect.contains(x, y) || touchFlags == 0) {
+                    // Found it
+                    touchedWin = child;
+                    break;
+                }
+            }
+
+            return touchedWin;
+        }
+    }
+
+    DragState mDragState = null;
+    private final InputHandler mDragInputHandler = new BaseInputHandler() {
+        @Override
+        public void handleMotion(MotionEvent event, Runnable finishedCallback) {
+            boolean endDrag = false;
+            final float newX = event.getRawX();
+            final float newY = event.getRawY();
+
+            try {
+                if (mDragState != null) {
+                    switch (event.getAction()) {
+                    case MotionEvent.ACTION_DOWN: {
+                        if (DEBUG_DRAG) {
+                            Slog.w(TAG, "Unexpected ACTION_DOWN in drag layer");
+                        }
+                    } break;
+
+                    case MotionEvent.ACTION_MOVE: {
+                        synchronized (mWindowMap) {
+                            // move the surface to the latest touch point
+                            mDragState.mSurface.openTransaction();
+                            mDragState.mSurface.setPosition((int)(newX - mDragState.mThumbOffsetX),
+                                    (int)(newY - mDragState.mThumbOffsetY));
+                            mDragState.mSurface.closeTransaction();
+
+                            // tell the involved window(s) where we are
+                            mDragState.notifyMoveLw(newX, newY);
+                        }
+                    } break;
+
+                    case MotionEvent.ACTION_UP: {
+                        if (DEBUG_DRAG) Slog.d(TAG, "Got UP on move channel; dropping at "
+                                + newX + "," + newY);
+                        synchronized (mWindowMap) {
+                            mDragState.notifyDropLw(newX, newY);
+                        }
+                        endDrag = true;
+                    } break;
+
+                    case MotionEvent.ACTION_CANCEL: {
+                        if (DEBUG_DRAG) Slog.d(TAG, "Drag cancelled!");
+                        endDrag = true;
+                    } break;
+                    }
+
+                    if (endDrag) {
+                        if (DEBUG_DRAG) Slog.d(TAG, "Drag ended; tearing down state");
+                        // tell all the windows that the drag has ended
+                        mDragState.broadcastDragEnded();
+
+                        // stop intercepting input
+                        mDragState.unregister();
+                        mInputMonitor.updateInputWindowsLw();
+
+                        // free our resources and drop all the object references
+                        mDragState.reset();
+                        mDragState = null;
+                    }
+                }
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception caught by drag handleMotion", e);
+            } finally {
+                finishedCallback.run();
+            }
+        }
+    };
+
+    /**
      * Whether the UI is currently running in touch mode (not showing
      * navigational focus because the user is directly pressing the screen).
      */
@@ -5046,7 +5358,61 @@
         mPolicy.adjustConfigurationLw(config);
         return true;
     }
-    
+
+    // -------------------------------------------------------------
+    // Drag and drop
+    // -------------------------------------------------------------
+
+    IBinder prepareDragSurface(IWindow window, SurfaceSession session,
+            boolean localOnly, int width, int height, Surface outSurface) {
+        if (DEBUG_DRAG) {
+            Slog.d(TAG, "prepare drag surface: w=" + width + " h=" + height
+                    + " local=" + localOnly + " win=" + window
+                    + " asbinder=" + window.asBinder());
+        }
+
+        final int callerPid = Binder.getCallingPid();
+        final long origId = Binder.clearCallingIdentity();
+        IBinder token = null;
+
+        try {
+            synchronized (mWindowMap) {
+                try {
+                    // !!! TODO: fail if the given window does not currently have touch focus?
+
+                    if (mDragState == null) {
+                        Surface surface = new Surface(session, callerPid, "drag surface", 0,
+                                width, height, PixelFormat.TRANSLUCENT, Surface.HIDDEN);
+                        outSurface.copyFrom(surface);
+                        token = new Binder();
+                        mDragState = new DragState(token, surface, localOnly);
+                        mDragState.mSurface = surface;
+                        mDragState.mLocalOnly = localOnly;
+                        token = mDragState.mToken = new Binder();
+
+                        // 5 second timeout for this window to actually begin the drag
+                        mH.removeMessages(H.DRAG_START_TIMEOUT, window);
+                        Message msg = mH.obtainMessage(H.DRAG_START_TIMEOUT, window.asBinder());
+                        mH.sendMessageDelayed(msg, 5000);
+                    } else {
+                        Slog.w(TAG, "Drag already in progress");
+                    }
+                } catch (Surface.OutOfResourcesException e) {
+                    Slog.e(TAG, "Can't allocate drag surface w=" + width + " h=" + height, e);
+                    if (mDragState != null) {
+                        mDragState.reset();
+                        mDragState = null;
+                    }
+                    
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+
+        return token;
+    }
+
     // -------------------------------------------------------------
     // Input Events and Focus Management
     // -------------------------------------------------------------
@@ -5145,7 +5511,42 @@
             
             return null;
         }
-        
+
+        private void addDragInputWindow(InputWindowList windowList) {
+            final InputWindow inputWindow = windowList.add();
+            inputWindow.inputChannel = mDragState.mServerChannel;
+            inputWindow.name = "drag";
+            inputWindow.layoutParamsFlags = 0;
+            inputWindow.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
+            inputWindow.dispatchingTimeoutNanos = DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+            inputWindow.visible = true;
+            inputWindow.canReceiveKeys = false;
+            inputWindow.hasFocus = true;
+            inputWindow.hasWallpaper = false;
+            inputWindow.paused = false;
+            inputWindow.layer = mPolicy.windowTypeToLayerLw(inputWindow.layoutParamsType)
+                    * TYPE_LAYER_MULTIPLIER
+                    + TYPE_LAYER_OFFSET;
+            inputWindow.ownerPid = Process.myPid();
+            inputWindow.ownerUid = Process.myUid();
+
+            // The drag window covers the entire display
+            inputWindow.frameLeft = 0;
+            inputWindow.frameTop = 0;
+            inputWindow.frameRight = mDisplay.getWidth();
+            inputWindow.frameBottom = mDisplay.getHeight();
+            
+            inputWindow.visibleFrameLeft = inputWindow.frameLeft;
+            inputWindow.visibleFrameTop = inputWindow.frameTop;
+            inputWindow.visibleFrameRight = inputWindow.frameRight;
+            inputWindow.visibleFrameBottom = inputWindow.frameBottom;
+
+            inputWindow.touchableAreaLeft = inputWindow.frameLeft;
+            inputWindow.touchableAreaTop = inputWindow.frameTop;
+            inputWindow.touchableAreaRight = inputWindow.frameRight;
+            inputWindow.touchableAreaBottom = inputWindow.frameBottom;
+        }
+
         /* Updates the cached window information provided to the input dispatcher. */
         public void updateInputWindowsLw() {
             // Populate the input window list with information about all of the windows that
@@ -5154,6 +5555,16 @@
             // out to be difficult because only the native code knows for sure which window
             // currently has touch focus.
             final ArrayList<WindowState> windows = mWindows;
+
+            // If there's a drag in flight, provide a pseudowindow to catch drag input
+            final boolean inDrag = (mDragState != null);
+            if (inDrag) {
+                if (DEBUG_DRAG) {
+                    Log.d(TAG, "Inserting drag window");
+                }
+                addDragInputWindow(mTempInputWindows);
+            }
+
             final int N = windows.size();
             for (int i = N - 1; i >= 0; i--) {
                 final WindowState child = windows.get(i);
@@ -5169,7 +5580,13 @@
                 final boolean isVisible = child.isVisibleLw();
                 final boolean hasWallpaper = (child == mWallpaperTarget)
                         && (type != WindowManager.LayoutParams.TYPE_KEYGUARD);
-                
+
+                // If there's a drag in progress and 'child' is a potential drop target,
+                // make sure it's been told about the drag
+                if (inDrag && isVisible) {
+                    mDragState.sendDragStartedIfNeededLw(child);
+                }
+
                 // Add a window to our list of input windows.
                 final InputWindow inputWindow = mTempInputWindows.add();
                 inputWindow.inputChannel = child.mInputChannel;
@@ -5741,6 +6158,87 @@
             }
         }
 
+        /* Drag/drop */
+        public IBinder prepareDrag(IWindow window, boolean localOnly,
+                int width, int height, Surface outSurface) {
+            return prepareDragSurface(window, mSurfaceSession, localOnly,
+                    width, height, outSurface);
+        }
+
+        public boolean performDrag(IWindow window, IBinder dragToken,
+                float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+                ClipData data) {
+            if (DEBUG_DRAG) {
+                Slog.d(TAG, "perform drag: win=" + window + " data=" + data);
+            }
+
+            synchronized (mWindowMap) {
+                if (mDragState == null) {
+                    Slog.w(TAG, "No drag prepared");
+                    throw new IllegalStateException("performDrag() without prepareDrag()");
+                }
+
+                if (dragToken != mDragState.mToken) {
+                    Slog.w(TAG, "Performing mismatched drag");
+                    throw new IllegalStateException("performDrag() does not match prepareDrag()");
+                }
+
+                WindowState callingWin = windowForClientLocked(null, window, false);
+                if (callingWin == null) {
+                    Slog.w(TAG, "Bad requesting window " + window);
+                    return false;  // !!! TODO: throw here?
+                }
+
+                // !!! TODO: if input is not still focused on the initiating window, fail
+                // the drag initiation (e.g. an alarm window popped up just as the application
+                // called performDrag()
+
+                mH.removeMessages(H.DRAG_START_TIMEOUT, window.asBinder());
+
+                // !!! TODO: call into the input monitor to sever the current touch event flow
+                // and redirect to the drag "window"; also extract the current touch (x, y)
+                // in screen coordinates
+
+                mDragState.register();
+                mInputMonitor.updateInputWindowsLw();
+                mInputManager.transferTouchFocus(callingWin.mInputChannel,
+                        mDragState.mServerChannel);
+
+                mDragState.mData = data;
+                mDragState.broadcastDragStartedLw();
+
+                // remember the thumb offsets for later
+                mDragState.mThumbOffsetX = thumbCenterX;
+                mDragState.mThumbOffsetY = thumbCenterY;
+
+                // Make the surface visible at the proper location
+                final Surface surface = mDragState.mSurface;
+                surface.openTransaction();
+                try {
+                    surface.setPosition((int)(touchX - thumbCenterX),
+                            (int)(touchY - thumbCenterY));
+                    surface.setAlpha(.5f);
+                    surface.show();
+                } finally {
+                    surface.closeTransaction();
+                }
+            }
+
+            return true;    // success!
+        }
+
+        public void dragRecipientEntered(IWindow window) {
+            if (DEBUG_DRAG) {
+                Slog.d(TAG, "Drag into new candidate view @ " + window);
+            }
+        }
+
+        public void dragRecipientExited(IWindow window) {
+            if (DEBUG_DRAG) {
+                Slog.d(TAG, "Drag from old candidate view @ " + window);
+            }
+        }
+
         public void setWallpaperPosition(IBinder window, float x, float y, float xStep, float yStep) {
             synchronized(mWindowMap) {
                 long ident = Binder.clearCallingIdentity();
@@ -6882,6 +7380,15 @@
         }
 
         /**
+         * Can this window possibly be a drag/drop target?  The test here is
+         * a combination of the above "visible now" with the check that the
+         * Input Manager uses when discarding windows from input consideration.
+         */
+        boolean isPotentialDragTarget() {
+            return isVisibleNow() && (mInputChannel != null) && !mRemoved;
+        }
+
+        /**
          * Same as isVisible(), but we also count it as visible between the
          * call to IWindowSession.add() and the first relayout().
          */
@@ -7810,6 +8317,7 @@
         public static final int APP_FREEZE_TIMEOUT = 17;
         public static final int SEND_NEW_CONFIGURATION = 18;
         public static final int REPORT_WINDOWS_CHANGE = 19;
+        public static final int DRAG_START_TIMEOUT = 20;
 
         private Session mLastReportedHold;
 
@@ -8152,6 +8660,20 @@
                     break;
                 }
 
+                case DRAG_START_TIMEOUT: {
+                    IBinder win = (IBinder)msg.obj;
+                    if (DEBUG_DRAG) {
+                        Slog.w(TAG, "Timeout starting drag by win " + win);
+                    }
+                    synchronized (mWindowMap) {
+                        // !!! TODO: ANR the app that has failed to start the drag in time
+                        if (mDragState != null) {
+                            mDragState.reset();
+                            mDragState = null;
+                        }
+                    }
+                }
+
             }
         }
     }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 5c4b919..1eab7fc7 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -113,9 +113,15 @@
 import android.view.WindowManager;
 import android.view.WindowManagerPolicy;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
@@ -6123,6 +6129,76 @@
         return mSystemReady;
     }
     
+    private static File getCalledPreBootReceiversFile() {
+        File dataDir = Environment.getDataDirectory();
+        File systemDir = new File(dataDir, "system");
+        File fname = new File(systemDir, "called_pre_boots.dat");
+        return fname;
+    }
+    
+    private static ArrayList<ComponentName> readLastDonePreBootReceivers() {
+        ArrayList<ComponentName> lastDoneReceivers = new ArrayList<ComponentName>();
+        File file = getCalledPreBootReceiversFile();
+        FileInputStream fis = null;
+        try {
+            fis = new FileInputStream(file);
+            DataInputStream dis = new DataInputStream(new BufferedInputStream(fis, 2048));
+            int vers = dis.readInt();
+            String codename = dis.readUTF();
+            if (vers == android.os.Build.VERSION.SDK_INT
+                    && codename.equals(android.os.Build.VERSION.CODENAME)) {
+                int num = dis.readInt();
+                while (num > 0) {
+                    num--;
+                    String pkg = dis.readUTF();
+                    String cls = dis.readUTF();
+                    lastDoneReceivers.add(new ComponentName(pkg, cls));
+                }
+            }
+        } catch (FileNotFoundException e) {
+        } catch (IOException e) {
+            Slog.w(TAG, "Failure reading last done pre-boot receivers", e);
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+        return lastDoneReceivers;
+    }
+    
+    private static void writeLastDonePreBootReceivers(ArrayList<ComponentName> list) {
+        File file = getCalledPreBootReceiversFile();
+        FileOutputStream fos = null;
+        DataOutputStream dos = null;
+        try {
+            Slog.i(TAG, "Writing new set of last done pre-boot receivers...");
+            fos = new FileOutputStream(file);
+            dos = new DataOutputStream(new BufferedOutputStream(fos, 2048));
+            dos.writeInt(android.os.Build.VERSION.SDK_INT);
+            dos.writeUTF(android.os.Build.VERSION.CODENAME);
+            dos.writeInt(list.size());
+            for (int i=0; i<list.size(); i++) {
+                dos.writeUTF(list.get(i).getPackageName());
+                dos.writeUTF(list.get(i).getClassName());
+            }
+        } catch (IOException e) {
+            Slog.w(TAG, "Failure writing last done pre-boot receivers", e);
+            file.delete();
+        } finally {
+            if (dos != null) {
+                try {
+                    dos.close();
+                } catch (IOException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+    
     public void systemReady(final Runnable goingCallback) {
         // In the simulator, startRunning will never have been called, which
         // normally sets a few crucial variables. Do it here instead.
@@ -6157,9 +6233,24 @@
                         }
                     }
                     intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE);
+                    
+                    ArrayList<ComponentName> lastDoneReceivers = readLastDonePreBootReceivers();
+                    
+                    final ArrayList<ComponentName> doneReceivers = new ArrayList<ComponentName>();
                     for (int i=0; i<ris.size(); i++) {
                         ActivityInfo ai = ris.get(i).activityInfo;
-                        intent.setComponent(new ComponentName(ai.packageName, ai.name));
+                        ComponentName comp = new ComponentName(ai.packageName, ai.name);
+                        if (lastDoneReceivers.contains(comp)) {
+                            ris.remove(i);
+                            i--;
+                        }
+                    }
+                    
+                    for (int i=0; i<ris.size(); i++) {
+                        ActivityInfo ai = ris.get(i).activityInfo;
+                        ComponentName comp = new ComponentName(ai.packageName, ai.name);
+                        doneReceivers.add(comp);
+                        intent.setComponent(comp);
                         IIntentReceiver finisher = null;
                         if (i == ris.size()-1) {
                             finisher = new IIntentReceiver.Stub() {
@@ -6174,6 +6265,7 @@
                                             synchronized (ActivityManagerService.this) {
                                                 mDidUpdate = true;
                                             }
+                                            writeLastDonePreBootReceivers(doneReceivers);
                                             systemReady(goingCallback);
                                         }
                                     });
@@ -6213,19 +6305,19 @@
             }
         }
         
-        if (procsToKill != null) {
-            synchronized(this) {
+        synchronized(this) {
+            if (procsToKill != null) {
                 for (int i=procsToKill.size()-1; i>=0; i--) {
                     ProcessRecord proc = procsToKill.get(i);
                     Slog.i(TAG, "Removing system update proc: " + proc);
                     removeProcessLocked(proc, true);
                 }
-
-                // Now that we have cleaned up any update processes, we
-                // are ready to start launching real processes and know that
-                // we won't trample on them any more.
-                mProcessesReady = true;
             }
+            
+            // Now that we have cleaned up any update processes, we
+            // are ready to start launching real processes and know that
+            // we won't trample on them any more.
+            mProcessesReady = true;
         }
         
         Slog.i(TAG, "System now ready");
@@ -7741,7 +7833,7 @@
     
             pw.println(" ");
             pw.println("Receiver Resolver Table:");
-            mReceiverResolver.dump(pw, null, "  ", null);
+            mReceiverResolver.dump(pw, null, "  ", null, false);
             needSep = true;
         }
         
diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java
index bf4db80..6bd89cc 100644
--- a/services/java/com/android/server/am/ActivityRecord.java
+++ b/services/java/com/android/server/am/ActivityRecord.java
@@ -444,10 +444,11 @@
                     sb.append(shortComponentName);
                     sb.append(": ");
                     TimeUtils.formatDuration(thisTime, sb);
-                    sb.append(" (total ");
-                    TimeUtils.formatDuration(totalTime, sb);
-                    sb.append(totalTime);
-                    sb.append(")");
+                    if (thisTime != totalTime) {
+                        sb.append(" (total ");
+                        TimeUtils.formatDuration(totalTime, sb);
+                        sb.append(")");
+                    }
                     Log.i(ActivityManagerService.TAG, sb.toString());
                 }
                 stack.reportActivityLaunchedLocked(false, this, thisTime, totalTime);
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index a4497d6..30395c0 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -1881,7 +1881,7 @@
             String resultWho, int requestCode,
             int callingPid, int callingUid, boolean onlyIfNeeded,
             boolean componentSpecified) {
-        Slog.i(TAG, "Starting activity: " + intent);
+        Slog.i(TAG, "Starting: " + intent);
 
         ActivityRecord sourceRecord = null;
         ActivityRecord resultRecord = null;
diff --git a/services/java/com/android/server/am/UsageStatsService.java b/services/java/com/android/server/am/UsageStatsService.java
index 3f15d0a..8463b5a 100644
--- a/services/java/com/android/server/am/UsageStatsService.java
+++ b/services/java/com/android/server/am/UsageStatsService.java
@@ -57,6 +57,7 @@
 public final class UsageStatsService extends IUsageStats.Stub {
     public static final String SERVICE_NAME = "usagestats";
     private static final boolean localLOGV = false;
+    private static final boolean REPORT_UNEXPECTED = false;
     private static final String TAG = "UsageStats";
     
     // Current on-disk Parcel version
@@ -404,11 +405,11 @@
                 new Thread("UsageStatsService_DiskWriter") {
                     public void run() {
                         try {
-                            Slog.d(TAG, "Disk writer thread starting.");
+                            if (localLOGV) Slog.d(TAG, "Disk writer thread starting.");
                             writeStatsToFile(true);
                         } finally {
                             mUnforcedDiskWriteRunning.set(false);
-                            Slog.d(TAG, "Disk writer thread ending.");
+                            if (localLOGV) Slog.d(TAG, "Disk writer thread ending.");
                         }
                     }
                 }.start();
@@ -458,7 +459,7 @@
                 }
             }
         }
-        Slog.d(TAG, "Dumped usage stats.");
+        if (localLOGV) Slog.d(TAG, "Dumped usage stats.");
     }
 
     private void writeStatsFLOCK(File file) throws IOException {
@@ -493,7 +494,7 @@
     }
     
     public void shutdown() {
-        Slog.w(TAG, "Writing usage stats before shutdown...");
+        Slog.i(TAG, "Writing usage stats before shutdown...");
         writeStatsToFile(true);
     }
     
@@ -520,7 +521,7 @@
                 if (mLastResumedPkg != null) {
                     // We last resumed some other package...  just pause it now
                     // to recover.
-                    Slog.i(TAG, "Unexpected resume of " + pkgName
+                    if (REPORT_UNEXPECTED) Slog.i(TAG, "Unexpected resume of " + pkgName
                             + " while already resumed in " + mLastResumedPkg);
                     PkgUsageStatsExtended pus = mStats.get(mLastResumedPkg);
                     if (pus != null) {
@@ -559,7 +560,7 @@
                 return;
             }
             if (!mIsResumed) {
-                Slog.i(TAG, "Something wrong here, didn't expect "
+                if (REPORT_UNEXPECTED) Slog.i(TAG, "Something wrong here, didn't expect "
                         + pkgName + " to be paused");
                 return;
             }
diff --git a/services/java/com/android/server/location/GeocoderProxy.java b/services/java/com/android/server/location/GeocoderProxy.java
index 3c05da2..d9b49fd 100644
--- a/services/java/com/android/server/location/GeocoderProxy.java
+++ b/services/java/com/android/server/location/GeocoderProxy.java
@@ -50,17 +50,24 @@
         mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
     }
 
+    public void reconnect() {
+        synchronized (mServiceConnection) {
+            mContext.unbindService(mServiceConnection);
+            mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
+        }
+    }
+
     private class Connection implements ServiceConnection {
         public void onServiceConnected(ComponentName className, IBinder service) {
             Log.d(TAG, "onServiceConnected " + className);
-            synchronized (this) {
+            synchronized (mServiceConnection) {
                 mProvider = IGeocodeProvider.Stub.asInterface(service);
             }
         }
 
         public void onServiceDisconnected(ComponentName className) {
             Log.d(TAG, "onServiceDisconnected " + className);
-            synchronized (this) {
+            synchronized (mServiceConnection) {
                 mProvider = null;
             }
         }
diff --git a/services/java/com/android/server/location/LocationProviderProxy.java b/services/java/com/android/server/location/LocationProviderProxy.java
index 7dc9920..ef2056b 100644
--- a/services/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/java/com/android/server/location/LocationProviderProxy.java
@@ -45,6 +45,7 @@
 
     private final Context mContext;
     private final String mName;
+    private final String mServiceName;
     private ILocationProvider mProvider;
     private Handler mHandler;
     private final Connection mServiceConnection = new Connection();
@@ -65,14 +66,24 @@
             Handler handler) {
         mContext = context;
         mName = name;
+        mServiceName = serviceName;
         mHandler = handler;
         mContext.bindService(new Intent(serviceName), mServiceConnection, Context.BIND_AUTO_CREATE);
     }
 
+    public void reconnect() {
+        synchronized (mServiceConnection) {
+            // unbind first
+            mContext.unbindService(mServiceConnection);
+            mContext.bindService(new Intent(mServiceName), mServiceConnection,
+                Context.BIND_AUTO_CREATE);
+        }
+    }
+
     private class Connection implements ServiceConnection {
         public void onServiceConnected(ComponentName className, IBinder service) {
             Log.d(TAG, "LocationProviderProxy.onServiceConnected " + className);
-            synchronized (this) {
+            synchronized (mServiceConnection) {
                 mProvider = ILocationProvider.Stub.asInterface(service);
                 if (mProvider != null) {
                     mHandler.post(mServiceConnectedTask);
@@ -82,7 +93,7 @@
 
         public void onServiceDisconnected(ComponentName className) {
             Log.d(TAG, "LocationProviderProxy.onServiceDisconnected " + className);
-            synchronized (this) {
+            synchronized (mServiceConnection) {
                 mProvider = null;
             }
         }
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index e1e54fc..bf6840c 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -1289,6 +1289,7 @@
 static jboolean android_server_InputManager_nativeTransferTouchFocus(JNIEnv* env,
         jclass clazz, jobject fromChannelObj, jobject toChannelObj) {
     if (checkInputManagerUnitialized(env)) {
+        LOGD("input manager uninitialized; bailing");
         return false;
     }
 
@@ -1298,6 +1299,7 @@
             android_view_InputChannel_getInputChannel(env, toChannelObj);
 
     if (fromChannel == NULL || toChannel == NULL) {
+        LOGD("bailing because from=%p to=%p", fromChannel, toChannel);
         return false;
     }
 
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 1b21a8d..fb76720 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -271,6 +271,17 @@
     }
 }
 
+void Layer::drawForSreenShot() const
+{
+    bool currentFixedSize = mFixedSize;
+    bool currentBlending = mNeedsBlending;
+    const_cast<Layer*>(this)->mFixedSize = false;
+    const_cast<Layer*>(this)->mFixedSize = true;
+    LayerBase::drawForSreenShot();
+    const_cast<Layer*>(this)->mFixedSize = currentFixedSize;
+    const_cast<Layer*>(this)->mNeedsBlending = currentBlending;
+}
+
 void Layer::onDraw(const Region& clip) const
 {
     Texture tex(mBufferManager.getActiveTexture());
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 188da6a..caa6d17 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -70,6 +70,7 @@
     // LayerBase interface
     virtual void setGeometry(hwc_layer_t* hwcl);
     virtual void setPerFrameData(hwc_layer_t* hwcl);
+    virtual void drawForSreenShot() const;
     virtual void onDraw(const Region& clip) const;
     virtual uint32_t doTransaction(uint32_t transactionFlags);
     virtual void lockPageFlip(bool& recomputeVisibleRegions);
diff --git a/services/surfaceflinger/LayerBase.cpp b/services/surfaceflinger/LayerBase.cpp
index 3d049a7..14191cb 100644
--- a/services/surfaceflinger/LayerBase.cpp
+++ b/services/surfaceflinger/LayerBase.cpp
@@ -326,6 +326,12 @@
     onDraw(clip);
 }
 
+void LayerBase::drawForSreenShot() const
+{
+    const DisplayHardware& hw(graphicPlane(0).displayHardware());
+    onDraw( Region(hw.bounds()) );
+}
+
 void LayerBase::clearWithOpenGL(const Region& clip, GLclampf red,
                                 GLclampf green, GLclampf blue,
                                 GLclampf alpha) const
diff --git a/services/surfaceflinger/LayerBase.h b/services/surfaceflinger/LayerBase.h
index c66dc34..bdee05b 100644
--- a/services/surfaceflinger/LayerBase.h
+++ b/services/surfaceflinger/LayerBase.h
@@ -121,6 +121,7 @@
      * to perform the actual drawing.  
      */
     virtual void draw(const Region& clip) const;
+    virtual void drawForSreenShot() const;
     
     /**
      * onDraw - draws the surface.
diff --git a/services/surfaceflinger/LayerBuffer.cpp b/services/surfaceflinger/LayerBuffer.cpp
index fdf9abc..c060895 100644
--- a/services/surfaceflinger/LayerBuffer.cpp
+++ b/services/surfaceflinger/LayerBuffer.cpp
@@ -132,6 +132,12 @@
     LayerBase::unlockPageFlip(planeTransform, outDirtyRegion);    
 }
 
+void LayerBuffer::drawForSreenShot() const
+{
+    const DisplayHardware& hw(graphicPlane(0).displayHardware());
+    clearWithOpenGL( Region(hw.bounds()) );
+}
+
 void LayerBuffer::onDraw(const Region& clip) const
 {
     sp<Source> source(getSource());
diff --git a/services/surfaceflinger/LayerBuffer.h b/services/surfaceflinger/LayerBuffer.h
index 1c0bf83..fece858 100644
--- a/services/surfaceflinger/LayerBuffer.h
+++ b/services/surfaceflinger/LayerBuffer.h
@@ -64,6 +64,7 @@
     virtual sp<LayerBaseClient::Surface> createSurface() const;
     virtual status_t ditch();
     virtual void onDraw(const Region& clip) const;
+    virtual void drawForSreenShot() const;
     virtual uint32_t doTransaction(uint32_t flags);
     virtual void unlockPageFlip(const Transform& planeTransform, Region& outDirtyRegion);
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 17b98a6..e6bdfd1 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1653,9 +1653,117 @@
 
 // ---------------------------------------------------------------------------
 
+status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy,
+        sp<IMemoryHeap>* heap,
+        uint32_t* w, uint32_t* h, PixelFormat* f,
+        uint32_t sw, uint32_t sh)
+{
+    status_t result = PERMISSION_DENIED;
+
+    // only one display supported for now
+    if (UNLIKELY(uint32_t(dpy) >= DISPLAY_COUNT))
+        return BAD_VALUE;
+
+    if (!GLExtensions::getInstance().haveFramebufferObject())
+        return INVALID_OPERATION;
+
+    // get screen geometry
+    const DisplayHardware& hw(graphicPlane(dpy).displayHardware());
+    const uint32_t hw_w = hw.getWidth();
+    const uint32_t hw_h = hw.getHeight();
+
+    if ((sw > hw_w) || (sh > hw_h))
+        return BAD_VALUE;
+
+    sw = (!sw) ? hw_w : sw;
+    sh = (!sh) ? hw_h : sh;
+    const size_t size = sw * sh * 4;
+
+    // make sure to clear all GL error flags
+    while ( glGetError() != GL_NO_ERROR ) ;
+
+    // create a FBO
+    GLuint name, tname;
+    glGenRenderbuffersOES(1, &tname);
+    glBindRenderbufferOES(GL_RENDERBUFFER_OES, tname);
+    glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_RGBA8_OES, sw, sh);
+    glGenFramebuffersOES(1, &name);
+    glBindFramebufferOES(GL_FRAMEBUFFER_OES, name);
+    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES,
+            GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, tname);
+
+    GLenum status = glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES);
+    if (status == GL_FRAMEBUFFER_COMPLETE_OES) {
+
+        // invert everything, b/c glReadPixel() below will invert the FB
+        glViewport(0, 0, sw, sh);
+        glMatrixMode(GL_PROJECTION);
+        glPushMatrix();
+        glLoadIdentity();
+        glOrthof(0, hw_w, 0, hw_h, 0, 1);
+        glMatrixMode(GL_MODELVIEW);
+
+        // redraw the screen entirely...
+        glClearColor(0,0,0,1);
+        glClear(GL_COLOR_BUFFER_BIT);
+        const Vector< sp<LayerBase> >& layers(mVisibleLayersSortedByZ);
+        const size_t count = layers.size();
+        for (size_t i=0 ; i<count ; ++i) {
+            const sp<LayerBase>& layer(layers[i]);
+            layer->drawForSreenShot();
+        }
+
+        // XXX: this is needed on tegra
+        glScissor(0, 0, sw, sh);
+
+        // check for errors and return screen capture
+        if (glGetError() != GL_NO_ERROR) {
+            // error while rendering
+            result = INVALID_OPERATION;
+        } else {
+            // allocate shared memory large enough to hold the
+            // screen capture
+            sp<MemoryHeapBase> base(
+                    new MemoryHeapBase(size, 0, "screen-capture") );
+            void* const ptr = base->getBase();
+            if (ptr) {
+                // capture the screen with glReadPixels()
+                glReadPixels(0, 0, sw, sh, GL_RGBA, GL_UNSIGNED_BYTE, ptr);
+                if (glGetError() == GL_NO_ERROR) {
+                    *heap = base;
+                    *w = sw;
+                    *h = sh;
+                    *f = PIXEL_FORMAT_RGBA_8888;
+                    result = NO_ERROR;
+                }
+            } else {
+                result = NO_MEMORY;
+            }
+        }
+
+        glEnable(GL_SCISSOR_TEST);
+        glViewport(0, 0, hw_w, hw_h);
+        glMatrixMode(GL_PROJECTION);
+        glPopMatrix();
+        glMatrixMode(GL_MODELVIEW);
+
+
+    } else {
+        result = BAD_VALUE;
+    }
+
+    // release FBO resources
+    glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0);
+    glDeleteRenderbuffersOES(1, &tname);
+    glDeleteFramebuffersOES(1, &name);
+    return result;
+}
+
+
 status_t SurfaceFlinger::captureScreen(DisplayID dpy,
         sp<IMemoryHeap>* heap,
-        uint32_t* width, uint32_t* height, PixelFormat* format)
+        uint32_t* width, uint32_t* height, PixelFormat* format,
+        uint32_t sw, uint32_t sh)
 {
     // only one display supported for now
     if (UNLIKELY(uint32_t(dpy) >= DISPLAY_COUNT))
@@ -1671,12 +1779,15 @@
         uint32_t* w;
         uint32_t* h;
         PixelFormat* f;
+        uint32_t sw;
+        uint32_t sh;
         status_t result;
     public:
         MessageCaptureScreen(SurfaceFlinger* flinger, DisplayID dpy,
-                sp<IMemoryHeap>* heap, uint32_t* w, uint32_t* h, PixelFormat* f)
+                sp<IMemoryHeap>* heap, uint32_t* w, uint32_t* h, PixelFormat* f,
+                uint32_t sw, uint32_t sh)
             : flinger(flinger), dpy(dpy),
-              heap(heap), w(w), h(h), f(f), result(PERMISSION_DENIED)
+              heap(heap), w(w), h(h), f(f), sw(sw), sh(sh), result(PERMISSION_DENIED)
         {
         }
         status_t getResult() const {
@@ -1689,94 +1800,15 @@
             if (flinger->mSecureFrameBuffer)
                 return true;
 
-            // make sure to clear all GL error flags
-            while ( glGetError() != GL_NO_ERROR ) ;
+            result = flinger->captureScreenImplLocked(dpy,
+                    heap, w, h, f, sw, sh);
 
-            // get screen geometry
-            const DisplayHardware& hw(flinger->graphicPlane(dpy).displayHardware());
-            const uint32_t sw = hw.getWidth();
-            const uint32_t sh = hw.getHeight();
-            const Region screenBounds(hw.bounds());
-            const size_t size = sw * sh * 4;
-
-            // create a FBO
-            GLuint name, tname;
-            glGenRenderbuffersOES(1, &tname);
-            glBindRenderbufferOES(GL_RENDERBUFFER_OES, tname);
-            glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_RGBA8_OES, sw, sh);
-            glGenFramebuffersOES(1, &name);
-            glBindFramebufferOES(GL_FRAMEBUFFER_OES, name);
-            glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES,
-                    GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, tname);
-
-            GLenum status = glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES);
-            if (status == GL_FRAMEBUFFER_COMPLETE_OES) {
-
-                // invert everything, b/c glReadPixel() below will invert the FB
-                glMatrixMode(GL_PROJECTION);
-                glPushMatrix();
-                glLoadIdentity();
-                glOrthof(0, sw, 0, sh, 0, 1);
-                glMatrixMode(GL_MODELVIEW);
-
-                // redraw the screen entirely...
-                glClearColor(0,0,0,1);
-                glClear(GL_COLOR_BUFFER_BIT);
-                const Vector< sp<LayerBase> >& layers(
-                        flinger->mVisibleLayersSortedByZ);
-                const size_t count = layers.size();
-                for (size_t i=0 ; i<count ; ++i) {
-                    const sp<LayerBase>& layer(layers[i]);
-                    if (!strcmp(layer->getTypeId(), "LayerBuffer")) {
-                        // we cannot render LayerBuffer because it doens't
-                        // use OpenGL, and won't show-up in the FBO.
-                        continue;
-                    }
-                    layer->draw(screenBounds);
-                }
-
-                glMatrixMode(GL_PROJECTION);
-                glPopMatrix();
-                glMatrixMode(GL_MODELVIEW);
-
-                // check for errors and return screen capture
-                if (glGetError() != GL_NO_ERROR) {
-                    // error while rendering
-                    result = INVALID_OPERATION;
-                } else {
-                    // allocate shared memory large enough to hold the
-                    // screen capture
-                    sp<MemoryHeapBase> base(
-                            new MemoryHeapBase(size, 0, "screen-capture") );
-                    void* const ptr = base->getBase();
-                    if (ptr) {
-                        // capture the screen with glReadPixels()
-                        glReadPixels(0, 0, sw, sh, GL_RGBA, GL_UNSIGNED_BYTE, ptr);
-                        if (glGetError() == GL_NO_ERROR) {
-                            *heap = base;
-                            *w = sw;
-                            *h = sh;
-                            *f = PIXEL_FORMAT_RGBA_8888;
-                            result = NO_ERROR;
-                        }
-                    } else {
-                        result = NO_MEMORY;
-                    }
-                }
-            } else {
-                result = BAD_VALUE;
-            }
-
-            // release FBO resources
-            glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0);
-            glDeleteRenderbuffersOES(1, &tname);
-            glDeleteFramebuffersOES(1, &name);
             return true;
         }
     };
 
     sp<MessageBase> msg = new MessageCaptureScreen(this,
-            dpy, heap, width, height, format);
+            dpy, heap, width, height, format, sw, sh);
     status_t res = postMessageSync(msg);
     if (res == NO_ERROR) {
         res = static_cast<MessageCaptureScreen*>( msg.get() )->getResult();
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 6e9ecbd..732e57e 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -197,7 +197,9 @@
                                                       sp<IMemoryHeap>* heap,
                                                       uint32_t* width,
                                                       uint32_t* height,
-                                                      PixelFormat* format);
+                                                      PixelFormat* format,
+                                                      uint32_t reqWidth,
+                                                      uint32_t reqHeight);
 
             void                        screenReleased(DisplayID dpy);
             void                        screenAcquired(DisplayID dpy);
@@ -319,6 +321,11 @@
             void        commitTransaction();
 
 
+            status_t captureScreenImplLocked(DisplayID dpy,
+                    sp<IMemoryHeap>* heap,
+                    uint32_t* width, uint32_t* height, PixelFormat* format,
+                    uint32_t reqWidth = 0, uint32_t reqHeight = 0);
+
             friend class FreezeLock;
             sp<FreezeLock> getFreezeLock() const;
             inline void incFreezeCount() {
diff --git a/services/surfaceflinger/tests/screencap/screencap.cpp b/services/surfaceflinger/tests/screencap/screencap.cpp
index 9e893f4..6cf1504 100644
--- a/services/surfaceflinger/tests/screencap/screencap.cpp
+++ b/services/surfaceflinger/tests/screencap/screencap.cpp
@@ -42,7 +42,7 @@
     sp<IMemoryHeap> heap;
     uint32_t w, h;
     PixelFormat f;
-    status_t err = composer->captureScreen(0, &heap, &w, &h, &f);
+    status_t err = composer->captureScreen(0, &heap, &w, &h, &f, 0, 0);
     if (err != NO_ERROR) {
         fprintf(stderr, "screen capture failed: %s\n", strerror(-err));
         exit(0);
diff --git a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
index ae9750d..a967850 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
@@ -25,9 +25,10 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
+import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.PhoneLookup;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -37,7 +38,7 @@
 
 public class CallerInfoAsyncQuery {
 
-    private static final boolean DBG = false;
+    private static final boolean DBG = true; // STOPSHIP: disable debugging before ship
     private static final String LOG_TAG = "CallerInfoAsyncQuery";
 
     private static final int EVENT_NEW_QUERY = 1;
@@ -190,7 +191,7 @@
          */
         @Override
         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-            if (DBG) log("query complete for token: " + token);
+            if (DBG) log("##### onQueryComplete() #####   query complete for token: " + token);
 
             //get the cookie and notify the listener.
             CookieWrapper cw = (CookieWrapper) cookie;
@@ -228,6 +229,8 @@
                     mCallerInfo = new CallerInfo().markAsVoiceMail();
                 } else {
                     mCallerInfo = CallerInfo.getCallerInfo(mQueryContext, mQueryUri, cursor);
+                    if (DBG) log("==> Got mCallerInfo: " + mCallerInfo);
+
                     // Use the number entered by the user for display.
                     if (!TextUtils.isEmpty(cw.number)) {
                         CountryDetector detector = (CountryDetector) mQueryContext.getSystemService(
@@ -243,7 +246,7 @@
                 //notify that we can clean up the queue after this.
                 CookieWrapper endMarker = new CookieWrapper();
                 endMarker.event = EVENT_END_OF_QUEUE;
-                startQuery (token, endMarker, null, null, null, null, null);
+                startQuery(token, endMarker, null, null, null, null, null);
             }
 
             //notify the listener that the query is complete.
@@ -279,24 +282,82 @@
         cw.cookie = cookie;
         cw.event = EVENT_NEW_QUERY;
 
-        c.mHandler.startQuery (token, cw, contactRef, null, null, null, null);
+        c.mHandler.startQuery(token, cw, contactRef, null, null, null, null);
 
         return c;
     }
 
     /**
-     * Factory method to start query with a number
+     * Factory method to start the query based on a number.
+     *
+     * Note: if the number contains an "@" character we treat it
+     * as a SIP address, and look it up directly in the Data table
+     * rather than using the PhoneLookup table.
+     * TODO: But eventually we should expose two separate methods, one for
+     * numbers and one for SIP addresses, and then have
+     * PhoneUtils.startGetCallerInfo() decide which one to call based on
+     * the phone type of the incoming connection.
      */
     public static CallerInfoAsyncQuery startQuery(int token, Context context, String number,
             OnQueryCompleteListener listener, Object cookie) {
-        //construct the URI object and start Query.
-        Uri contactRef = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
+        if (DBG) {
+            log("##### CallerInfoAsyncQuery startQuery()... #####");
+            log("- number: " + number);
+            log("- cookie: " + cookie);
+        }
+
+        // Construct the URI object and query params, and start the query.
+
+        Uri contactRef;
+        String selection;
+        String[] selectionArgs;
+
+        if (PhoneNumberUtils.isUriNumber(number)) {
+            // "number" is really a SIP address.
+            if (DBG) log("  - Treating number as a SIP address: " + number);
+
+            // We look up SIP addresses directly in the Data table:
+            contactRef = Data.CONTENT_URI;
+
+            // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
+            //
+            // Also note we use "upper(data1)" in the WHERE clause, and
+            // uppercase the incoming SIP address, in order to do a
+            // case-insensitive match.
+            //
+            // TODO: need to confirm that the use of upper() doesn't
+            // prevent us from using the index!  (Linear scan of the whole
+            // contacts DB can be very slow.)
+            //
+            // TODO: May also need to normalize by adding "sip:" as a
+            // prefix, if we start storing SIP addresses that way in the
+            // database.
+
+            selection = "upper(" + Data.DATA1 + ")=?"
+                    + " AND "
+                    + Data.MIMETYPE + "='" + SipAddress.CONTENT_ITEM_TYPE + "'";
+            selectionArgs = new String[] { number.toUpperCase() };
+
+        } else {
+            // "number" is a regular phone number.  Use the PhoneLookup table:
+            contactRef = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
+            selection = null;
+            selectionArgs = null;
+        }
+
+        if (DBG) {
+            log("==> contactRef: " + contactRef);
+            log("==> selection: " + selection);
+            if (selectionArgs != null) {
+                for (int i = 0; i < selectionArgs.length; i++) {
+                    log("==> selectionArgs[" + i + "]: " + selectionArgs[i]);
+                }
+            }
+        }
 
         CallerInfoAsyncQuery c = new CallerInfoAsyncQuery();
         c.allocate(context, contactRef);
 
-        if (DBG) log("starting query for number: " + number + " handler: " + c.toString());
-
         //create cookieWrapper, start query
         CookieWrapper cw = new CookieWrapper();
         cw.listener = listener;
@@ -312,10 +373,15 @@
             cw.event = EVENT_NEW_QUERY;
         }
 
-        c.mHandler.startQuery (token, cw, contactRef, null, null, null, null);
-
+        c.mHandler.startQuery(token,
+                              cw,  // cookie
+                              contactRef,  // uri
+                              null,  // projection
+                              selection,  // selection
+                              selectionArgs,  // selectionArgs
+                              null);  // orderBy
         return c;
-   }
+    }
 
     /**
      * Method to add listeners to a currently running query
@@ -331,7 +397,7 @@
         cw.cookie = cookie;
         cw.event = EVENT_ADD_LISTENER;
 
-        mHandler.startQuery (token, cw, null, null, null, null, null);
+        mHandler.startQuery(token, cw, null, null, null, null, null);
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
index d753973..52839be 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
@@ -35,7 +35,7 @@
  *
  */
 public abstract class DataConnectionTracker extends Handler {
-    protected static final boolean DBG = true;
+    protected static final boolean DBG = false;
     protected final String LOG_TAG = "DataConnectionTracker";
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java b/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java
index d93852c..189d97d 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java
@@ -56,7 +56,7 @@
      * 0, with the resulting code of 0x20.
      *
      * Note this mapping is also equivalent to that used by both the
-     * IS5 and the IS-91 encodings.  For the former this is defined
+     * IA5 and the IS-91 encodings.  For the former this is defined
      * using CCITT Rec. T.50 Tables 1 and 3.  For the latter IS 637 B,
      * Table 4.3.1.4.1-1 -- and note the encoding uses only 6 bits,
      * and hence only maps entries up to the '_' character.
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
index e0eac74..37dd971 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
@@ -133,14 +133,27 @@
                 return false;
             }
 
-            SipAudioCall sipAudioCall = (SipAudioCall) incomingCall;
-            Log.v(LOG_TAG, "  ++++++ taking call from: "
-                    + sipAudioCall.getPeerProfile().getUriString());
-            String localUri = sipAudioCall.getLocalProfile().getUriString();
-            if (localUri.equals(mProfile.getUriString())) {
-                boolean makeCallWait = foregroundCall.getState().isAlive();
-                ringingCall.initIncomingCall(sipAudioCall, makeCallWait);
-                return true;
+            try {
+                SipAudioCall sipAudioCall = (SipAudioCall) incomingCall;
+                Log.d(LOG_TAG, "+++ taking call from: "
+                        + sipAudioCall.getPeerProfile().getUriString());
+                String localUri = sipAudioCall.getLocalProfile().getUriString();
+                if (localUri.equals(mProfile.getUriString())) {
+                    boolean makeCallWait = foregroundCall.getState().isAlive();
+                    ringingCall.initIncomingCall(sipAudioCall, makeCallWait);
+                    if (sipAudioCall.getState()
+                            != SipSession.State.INCOMING_CALL) {
+                        // Peer cancelled the call!
+                        Log.d(LOG_TAG, "    call cancelled !!");
+                        ringingCall.reset();
+                    }
+                    return true;
+                }
+            } catch (Exception e) {
+                // Peer may cancel the call at any time during the time we hook
+                // up ringingCall with sipAudioCall. Clean up ringingCall when
+                // that happens.
+                ringingCall.reset();
             }
             return false;
         }
@@ -361,6 +374,11 @@
     }
 
     private class SipCall extends SipCallBase {
+        void reset() {
+            connections.clear();
+            setState(Call.State.IDLE);
+        }
+
         void switchWith(SipCall that) {
             synchronized (SipPhone.class) {
                 SipCall tmp = new SipCall();
@@ -447,6 +465,7 @@
                 if (state.isAlive()) {
                     Log.d(LOG_TAG, "hang up call: " + getState() + ": " + this
                             + " on phone " + getPhone());
+                    setState(State.DISCONNECTING);
                     CallStateException excp = null;
                     for (Connection c : connections) {
                         try {
@@ -456,7 +475,6 @@
                         }
                     }
                     if (excp != null) throw excp;
-                    setState(State.DISCONNECTING);
                 } else {
                     Log.d(LOG_TAG, "hang up dead call: " + getState() + ": "
                             + this + " on phone " + getPhone());
@@ -633,13 +651,20 @@
                 }
                 synchronized (SipPhone.class) {
                     setState(Call.State.DISCONNECTED);
-                    mSipAudioCall.close();
-                    mOwner.onConnectionEnded(SipConnection.this);
-                    Log.v(LOG_TAG, "-------- connection ended: "
-                            + mPeer.getUriString() + ": "
-                            + mSipAudioCall.getState() + ", cause: "
-                            + getDisconnectCause() + ", on phone "
+                    SipAudioCall sipAudioCall = mSipAudioCall;
+                    mSipAudioCall = null;
+                    String sessionState = (sipAudioCall == null)
+                            ? ""
+                            : (sipAudioCall.getState() + ", ");
+                    Log.v(LOG_TAG, "--- connection ended: "
+                            + mPeer.getUriString() + ": " + sessionState
+                            + "cause: " + getDisconnectCause() + ", on phone "
                             + getPhone());
+                    if (sipAudioCall != null) {
+                        sipAudioCall.setListener(null);
+                        sipAudioCall.close();
+                    }
+                    mOwner.onConnectionEnded(SipConnection.this);
                 }
             }
 
@@ -793,14 +818,17 @@
             synchronized (SipPhone.class) {
                 Log.v(LOG_TAG, "hangup conn: " + mPeer.getUriString() + ": "
                         + mState + ": on phone " + getPhone().getPhoneName());
+                if (!mState.isAlive()) return;
                 try {
-                    if (mState.isAlive()) {
-                        if (mSipAudioCall != null) mSipAudioCall.endCall();
-                        setState(Call.State.DISCONNECTING);
-                        setDisconnectCause(DisconnectCause.LOCAL);
+                    SipAudioCall sipAudioCall = mSipAudioCall;
+                    if (sipAudioCall != null) {
+                        sipAudioCall.setListener(null);
+                        sipAudioCall.endCall();
                     }
                 } catch (SipException e) {
                     throw new CallStateException("hangup(): " + e);
+                } finally {
+                    mAdapter.onCallEnded(DisconnectCause.LOCAL);
                 }
             }
         }
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index af71a0f..4be41b9 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -164,6 +164,15 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        
+        <activity
+                android:name="GradientsActivity"
+                android:label="_Gradients">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
 
         <activity
                 android:name="ShadersActivity"
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java
new file mode 100644
index 0000000..b70f3a9
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.LinearGradient;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Shader;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class GradientsActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(new ShadersView(this));
+    }
+
+    static class ShadersView extends View {
+        private final Paint mPaint;
+        private final float mDrawWidth;
+        private final float mDrawHeight;
+        private final LinearGradient mGradient;
+        private final Matrix mMatrix;
+
+        ShadersView(Context c) {
+            super(c);
+
+            mDrawWidth = 200;
+            mDrawHeight = 200;
+
+            mGradient = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP);
+            mMatrix = new Matrix();
+
+            mPaint = new Paint();
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            canvas.drawRGB(255, 255, 255);
+
+            // Gradients
+            canvas.save();
+            float top = 40.0f;
+            float right = 40.0f + mDrawWidth;
+            float left = 40.0f;
+            float bottom = 40.0f + mDrawHeight;
+
+            mPaint.setShader(mGradient);
+
+            mMatrix.setScale(1, mDrawWidth);
+            mMatrix.postRotate(90);
+            mMatrix.postTranslate(right, top);
+            mGradient.setLocalMatrix(mMatrix);
+            canvas.drawRect(right - mDrawWidth, top, right, top + mDrawHeight, mPaint);
+
+            top += 40.0f + mDrawHeight;
+            bottom += 40.0f + mDrawHeight;
+            
+            mMatrix.setScale(1, mDrawHeight);
+            mMatrix.postTranslate(left, top);
+            mGradient.setLocalMatrix(mMatrix);
+            canvas.drawRect(left, top, right, top + mDrawHeight, mPaint);
+            
+            left += 40.0f + mDrawWidth;
+            right += 40.0f + mDrawWidth;
+            top -= 40.0f + mDrawHeight;
+            bottom -= 40.0f + mDrawHeight;
+
+            mMatrix.setScale(1, mDrawHeight);
+            mMatrix.postRotate(180);
+            mMatrix.postTranslate(left, bottom);
+            mGradient.setLocalMatrix(mMatrix);
+            canvas.drawRect(left, bottom - mDrawHeight, right, bottom, mPaint);
+
+            top += 40.0f + mDrawHeight;
+            bottom += 40.0f + mDrawHeight;
+            
+            mMatrix.setScale(1, mDrawWidth);
+            mMatrix.postRotate(-90);
+            mMatrix.postTranslate(left, top);
+            mGradient.setLocalMatrix(mMatrix);
+            canvas.drawRect(left, top, left + mDrawWidth, bottom, mPaint);
+           
+            canvas.restore();
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ShadersActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ShadersActivity.java
index 9c8e7ec..2db1071 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ShadersActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ShadersActivity.java
@@ -77,8 +77,13 @@
             m2.setScale(0.5f, 0.5f);
             mScaledShader.setLocalMatrix(m2);
 
-            mHorGradient = new LinearGradient(0.0f, 0.0f, mDrawWidth, 0.0f,
+            mHorGradient = new LinearGradient(0.0f, 0.0f, 1.0f, 0.0f,
                     Color.RED, Color.GREEN, Shader.TileMode.CLAMP);
+            Matrix m3 = new Matrix();
+            m3.setScale(mDrawHeight, 1.0f);
+            m3.postRotate(-90.0f);
+            m3.postTranslate(0.0f, mDrawHeight);
+            mHorGradient.setLocalMatrix(m3);
             
             mDiagGradient = new LinearGradient(0.0f, 0.0f, mDrawWidth / 1.5f, mDrawHeight,
                     Color.BLUE, Color.MAGENTA, Shader.TileMode.CLAMP);
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/TransparentListActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TransparentListActivity.java
index f47b00f..763169a 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/TransparentListActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TransparentListActivity.java
@@ -87,6 +87,7 @@
         ListView list = (ListView) findViewById(R.id.list);
         list.setAdapter(adapter);
         list.setCacheColorHint(0);
+        list.setVerticalFadingEdgeEnabled(true);
 
         registerForContextMenu(list);
     }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index f91f601..0553f5e 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -30,6 +30,8 @@
 import com.android.tools.layoutlib.create.MethodAdapter;
 import com.android.tools.layoutlib.create.OverrideMethod;
 
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -46,6 +48,7 @@
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
 import android.view.BridgeInflater;
+import android.view.DragEvent;
 import android.view.InputChannel;
 import android.view.IWindow;
 import android.view.IWindowSession;
@@ -1073,6 +1076,33 @@
         }
 
         @SuppressWarnings("unused")
+        public IBinder prepareDrag(IWindow window, boolean localOnly,
+                int thumbnailWidth, int thumbnailHeight, Surface outSurface)
+                throws RemoteException {
+            // pass for now
+            return null;
+        }
+
+        @SuppressWarnings("unused")
+        public boolean performDrag(IWindow window, IBinder dragToken,
+                float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+                ClipData data)
+                throws RemoteException {
+            // pass for now
+            return false;
+        }
+
+        @SuppressWarnings("unused")
+        public void dragRecipientEntered(IWindow window) throws RemoteException {
+            // pass for now
+        }
+
+        @SuppressWarnings("unused")
+        public void dragRecipientExited(IWindow window) throws RemoteException {
+            // pass for now
+        }
+
+        @SuppressWarnings("unused")
         public void setWallpaperPosition(IBinder window, float x, float y,
             float xStep, float yStep) {
             // pass for now.
@@ -1170,6 +1200,11 @@
             // pass for now.
         }
 
+        @SuppressWarnings("unused")
+        public void dispatchDragEvent(DragEvent event) {
+            // pass for now.
+        }
+
         public IBinder asBinder() {
             // pass for now.
             return null;
diff --git a/tools/obbtool/mkobb.sh b/tools/obbtool/mkobb.sh
index f4cae9a1..1987696 100755
--- a/tools/obbtool/mkobb.sh
+++ b/tools/obbtool/mkobb.sh
@@ -21,7 +21,7 @@
 MOUNTDIR=/tmp
 
 # Presets. Changing these will probably break your OBB on the device
-CRYPTO=blowfish
+CRYPTO=twofish
 FS=vfat
 MKFS=mkfs.vfat
 LOSETUP=losetup
@@ -122,7 +122,12 @@
         rmdir ${temp_mount}
     fi
     if [ "x${loop_dev}" != "x" ]; then \
-        ${LOSETUPBIN} -d ${loop_dev}
+        if [ ${use_crypto} -eq 1 ]; then \
+            dmsetup remove -f ${loop_dev}
+            ${LOSETUPBIN} -d ${old_loop_dev}
+        else \
+            ${LOSETUPBIN} -d ${loop_dev}
+        fi
     fi
     if [ "x${tempfile}" != "x" -a -f "${tempfile}" ]; then \
         rm -f ${tempfile}
@@ -202,7 +207,7 @@
 
 tempfile=$(tempfile -d ${outdir}) || ( echo "ERROR: couldn't create temporary file in ${outdir}"; exit 1 )
 
-block_count=`du --apparent-size --block-size=512 ${directory} | awk '{ print $1; }'`
+block_count=`du -s --apparent-size --block-size=512 ${directory} | awk '{ print $1; }'`
 if [ $? -ne 0 ]; then \
     echo "ERROR: Couldn't read size of input directory ${directory}"
     exit 1
@@ -216,12 +221,14 @@
 
 loop_dev=$(${LOSETUPBIN} -f) || ( echo "ERROR: losetup wouldn't tell us the next unused device"; exit 1 )
 
+${LOSETUPBIN} ${loop_dev} ${tempfile} || ( echo "ERROR: couldn't create loopback device"; exit 1 )
+
 if [ ${use_crypto} -eq 1 ]; then \
-    keyfile=$(tempfile -d ${outdir}) || ( echo "ERROR: could not create temporary key file"; exit 1 )
-    ${LOSETUPBIN} -p 5 -e ${CRYPTO} ${loop_dev} ${tempfile} 5< ${keyfile} || ( echo "ERROR: couldn't create loopback device"; exit 1 )
-    rm -f ${keyfile}
-else \
-    ${LOSETUPBIN} ${loop_dev} ${tempfile} || ( echo "ERROR: couldn't create loopback device"; exit 1 )
+    hashed_key=`echo -n "${key}" | md5sum | awk '{ print $1 }'`
+    unique_dm_name=`basename ${tempfile}`
+    echo "0 `blockdev --getsize ${loop_dev}` crypt ${CRYPTO} ${hashed_key} 0 ${loop_dev} 0" | dmsetup create ${unique_dm_name}
+    old_loop_dev=${loop_dev}
+    loop_dev=/dev/mapper/${unique_dm_name}
 fi
 
 #
@@ -252,7 +259,12 @@
 #
 umount ${temp_mount}
 rmdir ${temp_mount}
-${LOSETUPBIN} -d ${loop_dev}
+if [ ${use_crypto} -eq 1 ]; then \
+    dmsetup remove -f ${loop_dev}
+    ${LOSETUPBIN} -d ${old_loop_dev}
+else \
+    ${LOSETUPBIN} -d ${loop_dev}
+fi
 mv ${tempfile} ${filename}
 
 trap - ERR
diff --git a/voip/java/android/net/rtp/AudioCodec.java b/voip/java/android/net/rtp/AudioCodec.java
index dfa6841..3877aeb 100644
--- a/voip/java/android/net/rtp/AudioCodec.java
+++ b/voip/java/android/net/rtp/AudioCodec.java
@@ -80,8 +80,7 @@
      */
     public static final AudioCodec AMR = new AudioCodec(97, "AMR/8000", null);
 
-    // TODO: add rest of the codecs when the native part is done.
-    private static final AudioCodec[] sCodecs = {GSM, PCMU, PCMA};
+    private static final AudioCodec[] sCodecs = {GSM_EFR, AMR, GSM, PCMU, PCMA};
 
     private AudioCodec(int type, String rtpmap, String fmtp) {
         this.type = type;
diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java
index 59631c1..a589fe9 100644
--- a/voip/java/android/net/sip/SipManager.java
+++ b/voip/java/android/net/sip/SipManager.java
@@ -173,7 +173,7 @@
             SipRegistrationListener listener) throws SipException {
         try {
             mSipService.open3(localProfile, incomingCallBroadcastAction,
-                    createRelay(listener));
+                    createRelay(listener, localProfile.getUriString()));
         } catch (RemoteException e) {
             throw new SipException("open()", e);
         }
@@ -191,7 +191,7 @@
             SipRegistrationListener listener) throws SipException {
         try {
             mSipService.setRegistrationListener(
-                    localProfileUri, createRelay(listener));
+                    localProfileUri, createRelay(listener, localProfileUri));
         } catch (RemoteException e) {
             throw new SipException("setRegistrationListener()", e);
         }
@@ -425,8 +425,8 @@
     public void register(SipProfile localProfile, int expiryTime,
             SipRegistrationListener listener) throws SipException {
         try {
-            ISipSession session = mSipService.createSession(
-                    localProfile, createRelay(listener));
+            ISipSession session = mSipService.createSession(localProfile,
+                    createRelay(listener, localProfile.getUriString()));
             session.register(expiryTime);
         } catch (RemoteException e) {
             throw new SipException("register()", e);
@@ -446,8 +446,8 @@
     public void unregister(SipProfile localProfile,
             SipRegistrationListener listener) throws SipException {
         try {
-            ISipSession session = mSipService.createSession(
-                    localProfile, createRelay(listener));
+            ISipSession session = mSipService.createSession(localProfile,
+                    createRelay(listener, localProfile.getUriString()));
             session.unregister();
         } catch (RemoteException e) {
             throw new SipException("unregister()", e);
@@ -475,8 +475,8 @@
     }
 
     private static ISipSessionListener createRelay(
-            SipRegistrationListener listener) {
-        return ((listener == null) ? null : new ListenerRelay(listener));
+            SipRegistrationListener listener, String uri) {
+        return ((listener == null) ? null : new ListenerRelay(listener, uri));
     }
 
     /**
@@ -512,15 +512,19 @@
 
     private static class ListenerRelay extends SipSessionAdapter {
         private SipRegistrationListener mListener;
+        private String mUri;
 
         // listener must not be null
-        public ListenerRelay(SipRegistrationListener listener) {
+        public ListenerRelay(SipRegistrationListener listener, String uri) {
             mListener = listener;
+            mUri = uri;
         }
 
         private String getUri(ISipSession session) {
             try {
-                return session.getLocalProfile().getUriString();
+                return ((session == null)
+                        ? mUri
+                        : session.getLocalProfile().getUriString());
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
diff --git a/voip/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java
index 0ff5586..405dff8 100644
--- a/voip/java/com/android/server/sip/SipService.java
+++ b/voip/java/com/android/server/sip/SipService.java
@@ -39,6 +39,7 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -49,6 +50,7 @@
 import java.net.DatagramSocket;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -64,6 +66,7 @@
  */
 public final class SipService extends ISipService.Stub {
     private static final String TAG = "SipService";
+    private static final boolean DEBUGV = false;
     private static final boolean DEBUG = true;
     private static final boolean DEBUG_TIMER = DEBUG && false;
     private static final int EXPIRY_TIME = 3600;
@@ -119,17 +122,19 @@
     }
 
     public synchronized SipProfile[] getListOfProfiles() {
-        SipProfile[] profiles = new SipProfile[mSipGroups.size()];
-        int i = 0;
+        boolean isCallerRadio = isCallerRadio();
+        ArrayList<SipProfile> profiles = new ArrayList<SipProfile>();
         for (SipSessionGroupExt group : mSipGroups.values()) {
-            profiles[i++] = group.getLocalProfile();
+            if (isCallerRadio || isCallerCreator(group)) {
+                profiles.add(group.getLocalProfile());
+            }
         }
-        return profiles;
+        return profiles.toArray(new SipProfile[profiles.size()]);
     }
 
     public void open(SipProfile localProfile) {
         localProfile.setCallingUid(Binder.getCallingUid());
-        if (localProfile.getAutoRegistration()) {
+        if (localProfile.getAutoRegistration() && isCallerRadio()) {
             openToReceiveCalls(localProfile);
         } else {
             openToMakeCalls(localProfile);
@@ -153,8 +158,14 @@
             String incomingCallBroadcastAction, ISipSessionListener listener) {
         localProfile.setCallingUid(Binder.getCallingUid());
         if (TextUtils.isEmpty(incomingCallBroadcastAction)) {
-            throw new RuntimeException(
-                    "empty broadcast action for incoming call");
+            Log.w(TAG, "empty broadcast action for incoming call");
+            return;
+        }
+        if (incomingCallBroadcastAction.equals(
+                SipManager.ACTION_SIP_INCOMING_CALL) && !isCallerRadio()) {
+            Log.w(TAG, "failed to open the profile; "
+                    + "the action string is reserved");
+            return;
         }
         if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": "
                 + incomingCallBroadcastAction + ": " + listener);
@@ -171,29 +182,64 @@
         }
     }
 
+    private boolean isCallerCreator(SipSessionGroupExt group) {
+        SipProfile profile = group.getLocalProfile();
+        return (profile.getCallingUid() == Binder.getCallingUid());
+    }
+
+    private boolean isCallerCreatorOrRadio(SipSessionGroupExt group) {
+        return (isCallerRadio() || isCallerCreator(group));
+    }
+
+    private boolean isCallerRadio() {
+        return (Binder.getCallingUid() == Process.PHONE_UID);
+    }
+
     public synchronized void close(String localProfileUri) {
-        SipSessionGroupExt group = mSipGroups.remove(localProfileUri);
-        if (group != null) {
-            notifyProfileRemoved(group.getLocalProfile());
-            group.close();
-            if (isWifiOn() && !anyOpened()) releaseWifiLock();
+        SipSessionGroupExt group = mSipGroups.get(localProfileUri);
+        if (group == null) return;
+        if (!isCallerCreatorOrRadio(group)) {
+            Log.d(TAG, "only creator or radio can close this profile");
+            return;
         }
+
+        group = mSipGroups.remove(localProfileUri);
+        notifyProfileRemoved(group.getLocalProfile());
+        group.close();
+        if (isWifiOn() && !anyOpened()) releaseWifiLock();
     }
 
     public synchronized boolean isOpened(String localProfileUri) {
         SipSessionGroupExt group = mSipGroups.get(localProfileUri);
-        return ((group != null) ? group.isOpened() : false);
+        if (group == null) return false;
+        if (isCallerCreatorOrRadio(group)) {
+            return group.isOpened();
+        } else {
+            Log.i(TAG, "only creator or radio can query on the profile");
+            return false;
+        }
     }
 
     public synchronized boolean isRegistered(String localProfileUri) {
         SipSessionGroupExt group = mSipGroups.get(localProfileUri);
-        return ((group != null) ? group.isRegistered() : false);
+        if (group == null) return false;
+        if (isCallerCreatorOrRadio(group)) {
+            return group.isRegistered();
+        } else {
+            Log.i(TAG, "only creator or radio can query on the profile");
+            return false;
+        }
     }
 
     public synchronized void setRegistrationListener(String localProfileUri,
             ISipSessionListener listener) {
         SipSessionGroupExt group = mSipGroups.get(localProfileUri);
-        if (group != null) group.setListener(listener);
+        if (group == null) return;
+        if (isCallerCreator(group)) {
+            group.setListener(listener);
+        } else {
+            Log.i(TAG, "only creator can set listener on the profile");
+        }
     }
 
     public synchronized ISipSession createSession(SipProfile localProfile,
@@ -234,6 +280,8 @@
             group = new SipSessionGroupExt(localProfile, null, null);
             mSipGroups.put(key, group);
             notifyProfileAdded(localProfile);
+        } else if (!isCallerCreator(group)) {
+            throw new SipException("only creator can access the profile");
         }
         return group;
     }
@@ -244,6 +292,9 @@
         String key = localProfile.getUriString();
         SipSessionGroupExt group = mSipGroups.get(key);
         if (group != null) {
+            if (!isCallerCreator(group)) {
+                throw new SipException("only creator can access the profile");
+            }
             group.setIncomingCallBroadcastAction(
                     incomingCallBroadcastAction);
             group.setListener(listener);
@@ -510,32 +561,44 @@
         }
     }
 
+    // KeepAliveProcess is controlled by AutoRegistrationProcess.
+    // All methods will be invoked in sync with SipService.this except realRun()
     private class KeepAliveProcess implements Runnable {
         private static final String TAG = "\\KEEPALIVE/";
         private static final int INTERVAL = 10;
         private SipSessionGroup.SipSessionImpl mSession;
+        private boolean mRunning = false;
 
         public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) {
             mSession = session;
         }
 
         public void start() {
+            if (mRunning) return;
+            mRunning = true;
             mTimer.set(INTERVAL * 1000, this);
         }
 
+        // timeout handler
         public void run() {
+            if (!mRunning) return;
+            final SipSessionGroup.SipSessionImpl session = mSession;
+
             // delegate to mExecutor
             getExecutor().addTask(new Runnable() {
                 public void run() {
-                    realRun();
+                    realRun(session);
                 }
             });
         }
 
-        private void realRun() {
+        // real timeout handler
+        private void realRun(SipSessionGroup.SipSessionImpl session) {
             synchronized (SipService.this) {
-                SipSessionGroup.SipSessionImpl session = mSession.duplicate();
-                if (DEBUG) Log.d(TAG, "~~~ keepalive");
+                if (notCurrentSession(session)) return;
+
+                session = session.duplicate();
+                if (DEBUGV) Log.v(TAG, "~~~ keepalive");
                 mTimer.cancel(this);
                 session.sendKeepAlive();
                 if (session.isReRegisterRequired()) {
@@ -547,8 +610,14 @@
         }
 
         public void stop() {
+            mRunning = false;
+            mSession = null;
             mTimer.cancel(this);
         }
+
+        private boolean notCurrentSession(ISipSession session) {
+            return (session != mSession) || !mRunning;
+        }
     }
 
     private class AutoRegistrationProcess extends SipSessionAdapter
@@ -561,13 +630,15 @@
         private long mExpiryTime;
         private int mErrorCode;
         private String mErrorMessage;
+        private boolean mRunning = false;
 
         private String getAction() {
             return toString();
         }
 
         public void start(SipSessionGroup group) {
-            if (mSession == null) {
+            if (!mRunning) {
+                mRunning = true;
                 mBackoff = 1;
                 mSession = (SipSessionGroup.SipSessionImpl)
                         group.createSession(this);
@@ -584,35 +655,24 @@
         }
 
         public void stop() {
-            stop(false);
-        }
-
-        private void stopButKeepStates() {
-            stop(true);
-        }
-
-        private void stop(boolean keepStates) {
-            if (mSession == null) return;
+            if (!mRunning) return;
+            mRunning = false;
+            mSession.setListener(null);
             if (mConnected && mRegistered) mSession.unregister();
+
             mTimer.cancel(this);
             if (mKeepAliveProcess != null) {
                 mKeepAliveProcess.stop();
                 mKeepAliveProcess = null;
             }
-            if (!keepStates) {
-                mSession = null;
-                mRegistered = false;
-            }
-        }
 
-        private boolean isStopped() {
-            return (mSession == null);
+            mRegistered = false;
+            setListener(mProxy.getListener());
         }
 
         public void setListener(ISipSessionListener listener) {
             synchronized (SipService.this) {
                 mProxy.setListener(listener);
-                if (mSession == null) return;
 
                 try {
                     int state = (mSession == null)
@@ -632,6 +692,18 @@
                             mProxy.onRegistrationFailed(mSession, mErrorCode,
                                     mErrorMessage);
                         }
+                    } else if (!mConnected) {
+                        mProxy.onRegistrationFailed(mSession,
+                                SipErrorCode.DATA_CONNECTION_LOST,
+                                "no data connection");
+                    } else if (!mRunning) {
+                        mProxy.onRegistrationFailed(mSession,
+                                SipErrorCode.CLIENT_ERROR,
+                                "registration not running");
+                    } else {
+                        mProxy.onRegistrationFailed(mSession,
+                                SipErrorCode.IN_PROGRESS,
+                                String.valueOf(state));
                     }
                 } catch (Throwable t) {
                     Log.w(TAG, "setListener(): " + t);
@@ -643,21 +715,29 @@
             return mRegistered;
         }
 
+        // timeout handler
         public void run() {
-            // delegate to mExecutor
-            getExecutor().addTask(new Runnable() {
-                public void run() {
-                    realRun();
-                }
-            });
+            synchronized (SipService.this) {
+                if (!mRunning) return;
+                final SipSessionGroup.SipSessionImpl session = mSession;
+
+                // delegate to mExecutor
+                getExecutor().addTask(new Runnable() {
+                    public void run() {
+                        realRun(session);
+                    }
+                });
+            }
         }
 
-        private void realRun() {
-            mErrorCode = SipErrorCode.NO_ERROR;
-            mErrorMessage = null;
-            if (DEBUG) Log.d(TAG, "~~~ registering");
+        // real timeout handler
+        private void realRun(SipSessionGroup.SipSessionImpl session) {
             synchronized (SipService.this) {
-                if (mConnected && !isStopped()) mSession.register(EXPIRY_TIME);
+                if (notCurrentSession(session)) return;
+                mErrorCode = SipErrorCode.NO_ERROR;
+                mErrorMessage = null;
+                if (DEBUG) Log.d(TAG, "~~~ registering");
+                if (mConnected) session.register(EXPIRY_TIME);
             }
         }
 
@@ -697,22 +777,29 @@
         public void onRegistering(ISipSession session) {
             if (DEBUG) Log.d(TAG, "onRegistering(): " + session);
             synchronized (SipService.this) {
-                if (!isStopped() && (session != mSession)) return;
+                if (notCurrentSession(session)) return;
+
                 mRegistered = false;
                 mProxy.onRegistering(session);
             }
         }
 
+        private boolean notCurrentSession(ISipSession session) {
+            if (session != mSession) {
+                ((SipSessionGroup.SipSessionImpl) session).setListener(null);
+                return true;
+            }
+            return !mRunning;
+        }
+
         @Override
         public void onRegistrationDone(ISipSession session, int duration) {
             if (DEBUG) Log.d(TAG, "onRegistrationDone(): " + session);
             synchronized (SipService.this) {
-                if (!isStopped() && (session != mSession)) return;
+                if (notCurrentSession(session)) return;
 
                 mProxy.onRegistrationDone(session, duration);
 
-                if (isStopped()) return;
-
                 if (duration > 0) {
                     mSession.clearReRegisterRequired();
                     mExpiryTime = SystemClock.elapsedRealtime()
@@ -751,17 +838,18 @@
             if (DEBUG) Log.d(TAG, "onRegistrationFailed(): " + session + ": "
                     + SipErrorCode.toString(errorCode) + ": " + message);
             synchronized (SipService.this) {
-                if (!isStopped() && (session != mSession)) return;
-                mErrorCode = errorCode;
-                mErrorMessage = message;
-                mProxy.onRegistrationFailed(session, errorCode, message);
+                if (notCurrentSession(session)) return;
 
                 if (errorCode == SipErrorCode.INVALID_CREDENTIALS) {
                     if (DEBUG) Log.d(TAG, "   pause auto-registration");
-                    stopButKeepStates();
-                } else if (!isStopped()) {
+                    stop();
+                } else {
                     onError();
                 }
+
+                mErrorCode = errorCode;
+                mErrorMessage = message;
+                mProxy.onRegistrationFailed(session, errorCode, message);
             }
         }
 
@@ -769,14 +857,11 @@
         public void onRegistrationTimeout(ISipSession session) {
             if (DEBUG) Log.d(TAG, "onRegistrationTimeout(): " + session);
             synchronized (SipService.this) {
-                if (!isStopped() && (session != mSession)) return;
+                if (notCurrentSession(session)) return;
+
                 mErrorCode = SipErrorCode.TIME_OUT;
                 mProxy.onRegistrationTimeout(session);
-
-                if (!isStopped()) {
-                    mRegistered = false;
-                    onError();
-                }
+                onError();
             }
         }
 
@@ -883,6 +968,7 @@
                 mConnected = connected;
             }
 
+            // timeout handler
             @Override
             public void run() {
                 // delegate to mExecutor
diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java
index 4321d7b..c68fa1b 100644
--- a/voip/java/com/android/server/sip/SipSessionGroup.java
+++ b/voip/java/com/android/server/sip/SipSessionGroup.java
@@ -334,12 +334,12 @@
             if (isRequestEvent(Request.INVITE, evt)) {
                 RequestEvent event = (RequestEvent) evt;
                 SipSessionImpl newSession = new SipSessionImpl(mProxy);
+                newSession.mState = SipSession.State.INCOMING_CALL;
                 newSession.mServerTransaction = mSipHelper.sendRinging(event,
                         generateTag());
                 newSession.mDialog = newSession.mServerTransaction.getDialog();
                 newSession.mInviteReceived = event;
                 newSession.mPeerProfile = createPeerProfile(event.getRequest());
-                newSession.mState = SipSession.State.INCOMING_CALL;
                 newSession.mPeerSessionDescription =
                         extractContent(event.getRequest());
                 addSipSession(newSession);
@@ -708,7 +708,6 @@
                 case SipSession.State.PINGING:
                     reset();
                     mReRegisterFlag = true;
-                    mState = SipSession.State.READY_TO_CALL;
                     break;
 
                 default:
@@ -877,6 +876,7 @@
         private boolean readyForCall(EventObject evt) throws SipException {
             // expect MakeCallCommand, RegisterCommand, DEREGISTER
             if (evt instanceof MakeCallCommand) {
+                mState = SipSession.State.OUTGOING_CALL;
                 MakeCallCommand cmd = (MakeCallCommand) evt;
                 mPeerProfile = cmd.getPeerProfile();
                 mClientTransaction = mSipHelper.sendInvite(mLocalProfile,
@@ -884,25 +884,24 @@
                         generateTag());
                 mDialog = mClientTransaction.getDialog();
                 addSipSession(this);
-                mState = SipSession.State.OUTGOING_CALL;
                 mProxy.onCalling(this);
                 startSessionTimer(cmd.getTimeout());
                 return true;
             } else if (evt instanceof RegisterCommand) {
+                mState = SipSession.State.REGISTERING;
                 int duration = ((RegisterCommand) evt).getDuration();
                 mClientTransaction = mSipHelper.sendRegister(mLocalProfile,
                         generateTag(), duration);
                 mDialog = mClientTransaction.getDialog();
                 addSipSession(this);
-                mState = SipSession.State.REGISTERING;
                 mProxy.onRegistering(this);
                 return true;
             } else if (DEREGISTER == evt) {
+                mState = SipSession.State.DEREGISTERING;
                 mClientTransaction = mSipHelper.sendRegister(mLocalProfile,
                         generateTag(), 0);
                 mDialog = mClientTransaction.getDialog();
                 addSipSession(this);
-                mState = SipSession.State.DEREGISTERING;
                 mProxy.onRegistering(this);
                 return true;
             }
@@ -913,11 +912,11 @@
             // expect MakeCallCommand(answering) , END_CALL cmd , Cancel
             if (evt instanceof MakeCallCommand) {
                 // answer call
+                mState = SipSession.State.INCOMING_CALL_ANSWERING;
                 mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived,
                         mLocalProfile,
                         ((MakeCallCommand) evt).getSessionDescription(),
                         mServerTransaction);
-                mState = SipSession.State.INCOMING_CALL_ANSWERING;
                 startSessionTimer(((MakeCallCommand) evt).getTimeout());
                 return true;
             } else if (END_CALL == evt) {
@@ -1009,8 +1008,8 @@
                 // RFC says that UA should not send out cancel when no
                 // response comes back yet. We are cheating for not checking
                 // response.
-                mSipHelper.sendCancel(mClientTransaction);
                 mState = SipSession.State.OUTGOING_CALL_CANCELING;
+                mSipHelper.sendCancel(mClientTransaction);
                 startSessionTimer(CANCEL_CALL_TIMER);
                 return true;
             }
@@ -1065,8 +1064,8 @@
                 return true;
             } else if (isRequestEvent(Request.INVITE, evt)) {
                 // got Re-INVITE
-                RequestEvent event = mInviteReceived = (RequestEvent) evt;
                 mState = SipSession.State.INCOMING_CALL;
+                RequestEvent event = mInviteReceived = (RequestEvent) evt;
                 mPeerSessionDescription = extractContent(event.getRequest());
                 mServerTransaction = null;
                 mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription);
@@ -1077,9 +1076,9 @@
                 return true;
             } else if (evt instanceof MakeCallCommand) {
                 // to change call
+                mState = SipSession.State.OUTGOING_CALL;
                 mClientTransaction = mSipHelper.sendReinvite(mDialog,
                         ((MakeCallCommand) evt).getSessionDescription());
-                mState = SipSession.State.OUTGOING_CALL;
                 startSessionTimer(((MakeCallCommand) evt).getTimeout());
                 return true;
             }
diff --git a/voip/jni/rtp/AmrCodec.cpp b/voip/jni/rtp/AmrCodec.cpp
new file mode 100644
index 0000000..f3ecac2
--- /dev/null
+++ b/voip/jni/rtp/AmrCodec.cpp
@@ -0,0 +1,268 @@
+/*
+ * Copyrightm (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string.h>
+
+#include "AudioCodec.h"
+
+#include "gsmamr_dec.h"
+#include "gsmamr_enc.h"
+
+namespace {
+
+const int gFrameBits[8] = {95, 103, 118, 134, 148, 159, 204, 244};
+
+//------------------------------------------------------------------------------
+
+// See RFC 4867 for the encoding details.
+
+class AmrCodec : public AudioCodec
+{
+public:
+    AmrCodec() {
+        if (AMREncodeInit(&mEncoder, &mSidSync, false)) {
+            mEncoder = NULL;
+        }
+        if (GSMInitDecode(&mDecoder, (Word8 *)"RTP")) {
+            mDecoder = NULL;
+        }
+    }
+
+    ~AmrCodec() {
+        if (mEncoder) {
+            AMREncodeExit(&mEncoder, &mSidSync);
+        }
+        if (mDecoder) {
+            GSMDecodeFrameExit(&mDecoder);
+        }
+    }
+
+    int set(int sampleRate, const char *fmtp);
+    int encode(void *payload, int16_t *samples);
+    int decode(int16_t *samples, void *payload, int length);
+
+private:
+    void *mEncoder;
+    void *mSidSync;
+    void *mDecoder;
+
+    int mMode;
+    int mModeSet;
+    bool mOctetAligned;
+};
+
+int AmrCodec::set(int sampleRate, const char *fmtp)
+{
+    // These parameters are not supported.
+    if (strcasestr(fmtp, "crc=1") || strcasestr(fmtp, "robust-sorting=1") ||
+        strcasestr(fmtp, "interleaving=")) {
+        return -1;
+    }
+
+    // Handle mode-set and octet-align.
+    char *modes = strcasestr(fmtp, "mode-set=");
+    if (modes) {
+        mMode = 0;
+        mModeSet = 0;
+        for (char c = *modes; c && c != ' '; c = *++modes) {
+            if (c >= '0' && c <= '7') {
+                int mode = c - '0';
+                if (mode > mMode) {
+                    mMode = mode;
+                }
+                mModeSet |= 1 << mode;
+            }
+        }
+    } else {
+        mMode = 7;
+        mModeSet = 0xFF;
+    }
+    mOctetAligned = (strcasestr(fmtp, "octet-align=1") != NULL);
+
+    // TODO: handle mode-change-*.
+
+    return (sampleRate == 8000 && mEncoder && mDecoder) ? 160 : -1;
+}
+
+int AmrCodec::encode(void *payload, int16_t *samples)
+{
+    unsigned char *bytes = (unsigned char *)payload;
+    Frame_Type_3GPP type;
+
+    int length = AMREncode(mEncoder, mSidSync, (Mode)mMode,
+        samples, bytes + 1, &type, AMR_TX_WMF);
+
+    if (type != mMode || length != (8 + gFrameBits[mMode] + 7) >> 3) {
+        return -1;
+    }
+
+    if (mOctetAligned) {
+        bytes[0] = 0xF0;
+        bytes[1] = (mMode << 3) | 0x04;
+        ++length;
+    } else {
+        // CMR = 15 (4-bit), F = 0 (1-bit), FT = mMode (4-bit), Q = 1 (1-bit).
+        bytes[0] = 0xFF;
+        bytes[1] = 0xC0 | (mMode << 1) | 1;
+
+        // Shift left 6 bits and update the length.
+        bytes[length + 1] = 0;
+        for (int i = 0; i <= length; ++i) {
+            bytes[i] = (bytes[i] << 6) | (bytes[i + 1] >> 2);
+        }
+        length = (10 + gFrameBits[mMode] + 7) >> 3;
+    }
+    return length;
+}
+
+int AmrCodec::decode(int16_t *samples, void *payload, int length)
+{
+    unsigned char *bytes = (unsigned char *)payload;
+    Frame_Type_3GPP type;
+    if (length < 2) {
+        return -1;
+    }
+    int request = bytes[0] >> 4;
+
+    if (mOctetAligned) {
+        if ((bytes[1] & 0xC4) != 0x04) {
+            return -1;
+        }
+        type = (Frame_Type_3GPP)(bytes[1] >> 3);
+        if (length != (16 + gFrameBits[type] + 7) >> 3) {
+            return -1;
+        }
+        length -= 2;
+        bytes += 2;
+    } else {
+        if ((bytes[0] & 0x0C) || !(bytes[1] & 0x40)) {
+            return -1;
+        }
+        type = (Frame_Type_3GPP)((bytes[0] << 1 | bytes[1] >> 7) & 0x07);
+        if (length != (10 + gFrameBits[type] + 7) >> 3) {
+            return -1;
+        }
+
+        // Shift left 2 bits and update the length.
+        --length;
+        for (int i = 1; i < length; ++i) {
+            bytes[i] = (bytes[i] << 2) | (bytes[i + 1] >> 6);
+        }
+        bytes[length] <<= 2;
+        length = (gFrameBits[type] + 7) >> 3;
+        ++bytes;
+    }
+
+    if (AMRDecode(mDecoder, type, bytes, samples, MIME_IETF) != length) {
+        return -1;
+    }
+
+    // Handle CMR
+    if (request < 8 && request != mMode) {
+        for (int i = request; i >= 0; --i) {
+            if (mModeSet & (1 << i)) {
+                mMode = request;
+                break;
+            }
+        }
+    }
+
+    return 160;
+}
+
+//------------------------------------------------------------------------------
+
+// See RFC 3551 for the encoding details.
+
+class GsmEfrCodec : public AudioCodec
+{
+public:
+    GsmEfrCodec() {
+        if (AMREncodeInit(&mEncoder, &mSidSync, false)) {
+            mEncoder = NULL;
+        }
+        if (GSMInitDecode(&mDecoder, (Word8 *)"RTP")) {
+            mDecoder = NULL;
+        }
+    }
+
+    ~GsmEfrCodec() {
+        if (mEncoder) {
+            AMREncodeExit(&mEncoder, &mSidSync);
+        }
+        if (mDecoder) {
+            GSMDecodeFrameExit(&mDecoder);
+        }
+    }
+
+    int set(int sampleRate, const char *fmtp) {
+        return (sampleRate == 8000 && mEncoder && mDecoder) ? 160 : -1;
+    }
+
+    int encode(void *payload, int16_t *samples);
+    int decode(int16_t *samples, void *payload, int length);
+
+private:
+    void *mEncoder;
+    void *mSidSync;
+    void *mDecoder;
+};
+
+int GsmEfrCodec::encode(void *payload, int16_t *samples)
+{
+    unsigned char *bytes = (unsigned char *)payload;
+    Frame_Type_3GPP type;
+
+    int length = AMREncode(mEncoder, mSidSync, MR122,
+        samples, bytes, &type, AMR_TX_WMF);
+
+    if (type == AMR_122 && length == 32) {
+        bytes[0] = 0xC0 | (bytes[1] >> 4);
+        for (int i = 1; i < 31; ++i) {
+            bytes[i] = (bytes[i] << 4) | (bytes[i + 1] >> 4);
+        }
+        return 31;
+    }
+    return -1;
+}
+
+int GsmEfrCodec::decode(int16_t *samples, void *payload, int length)
+{
+    unsigned char *bytes = (unsigned char *)payload;
+    if (length == 31 && (bytes[0] >> 4) == 0x0C) {
+        for (int i = 0; i < 30; ++i) {
+            bytes[i] = (bytes[i] << 4) | (bytes[i + 1] >> 4);
+        }
+        bytes[30] <<= 4;
+
+        if (AMRDecode(mDecoder, AMR_122, bytes, samples, MIME_IETF) == 31) {
+            return 160;
+        }
+    }
+    return -1;
+}
+
+} // namespace
+
+AudioCodec *newAmrCodec()
+{
+    return new AmrCodec;
+}
+
+AudioCodec *newGsmEfrCodec()
+{
+    return new GsmEfrCodec;
+}
diff --git a/voip/jni/rtp/Android.mk b/voip/jni/rtp/Android.mk
index 29683bd..5909c0d 100644
--- a/voip/jni/rtp/Android.mk
+++ b/voip/jni/rtp/Android.mk
@@ -27,6 +27,7 @@
 	rtp_jni.cpp
 
 LOCAL_SRC_FILES += \
+	AmrCodec.cpp \
 	G711Codec.cpp \
 	GsmCodec.cpp
 
@@ -34,13 +35,20 @@
 	libnativehelper \
 	libcutils \
 	libutils \
-	libmedia
+	libmedia \
+	libstagefright
 
 LOCAL_STATIC_LIBRARIES := libgsm
 
 LOCAL_C_INCLUDES += \
 	$(JNI_H_INCLUDE) \
-	external/libgsm/inc
+	external/libgsm/inc \
+	frameworks/base/media/libstagefright/codecs/amrnb/common/include \
+	frameworks/base/media/libstagefright/codecs/amrnb/common/ \
+	frameworks/base/media/libstagefright/codecs/amrnb/enc/include \
+	frameworks/base/media/libstagefright/codecs/amrnb/enc/src \
+	frameworks/base/media/libstagefright/codecs/amrnb/dec/include \
+	frameworks/base/media/libstagefright/codecs/amrnb/dec/src
 
 LOCAL_CFLAGS += -fvisibility=hidden
 
diff --git a/voip/jni/rtp/AudioCodec.cpp b/voip/jni/rtp/AudioCodec.cpp
index fc33ef2..2267ea0 100644
--- a/voip/jni/rtp/AudioCodec.cpp
+++ b/voip/jni/rtp/AudioCodec.cpp
@@ -21,6 +21,8 @@
 extern AudioCodec *newAlawCodec();
 extern AudioCodec *newUlawCodec();
 extern AudioCodec *newGsmCodec();
+extern AudioCodec *newAmrCodec();
+extern AudioCodec *newGsmEfrCodec();
 
 struct AudioCodecType {
     const char *name;
@@ -29,6 +31,8 @@
     {"PCMA", newAlawCodec},
     {"PCMU", newUlawCodec},
     {"GSM", newGsmCodec},
+    {"AMR", newAmrCodec},
+    {"GSM-EFR", newGsmEfrCodec},
     {NULL, NULL},
 };
 
diff --git a/voip/jni/rtp/AudioGroup.cpp b/voip/jni/rtp/AudioGroup.cpp
index f09edb6..5214518 100644
--- a/voip/jni/rtp/AudioGroup.cpp
+++ b/voip/jni/rtp/AudioGroup.cpp
@@ -57,9 +57,9 @@
 // a modulo operation on the index while accessing the array. However modulo can
 // be expensive on some platforms, such as ARM. Thus we round up the size of the
 // array to the nearest power of 2 and then use bitwise-and instead of modulo.
-// Currently we make it 256ms long and assume packet interval is 32ms or less.
-// The first 64ms is the place where samples get mixed. The rest 192ms is the
-// real jitter buffer. For a stream at 8000Hz it takes 4096 bytes. These numbers
+// Currently we make it 512ms long and assume packet interval is 40ms or less.
+// The first 80ms is the place where samples get mixed. The rest 432ms is the
+// real jitter buffer. For a stream at 8000Hz it takes 8192 bytes. These numbers
 // are chosen by experiments and each of them can be adjusted as needed.
 
 // Other notes:
@@ -69,7 +69,11 @@
 //   milliseconds. No floating points.
 // + If we cannot get enough CPU, we drop samples and simulate packet loss.
 // + Resampling is not done yet, so streams in one group must use the same rate.
-//   For the first release we might only support 8kHz and 16kHz.
+//   For the first release only 8000Hz is supported.
+
+#define BUFFER_SIZE     512
+#define HISTORY_SIZE    80
+#define MEASURE_PERIOD  2000
 
 class AudioStream
 {
@@ -85,7 +89,6 @@
     void encode(int tick, AudioStream *chain);
     void decode(int tick);
 
-private:
     enum {
         NORMAL = 0,
         SEND_ONLY = 1,
@@ -93,6 +96,7 @@
         LAST_MODE = 2,
     };
 
+private:
     int mMode;
     int mSocket;
     sockaddr_storage mRemote;
@@ -111,6 +115,7 @@
     int mBufferMask;
     int mBufferHead;
     int mBufferTail;
+    int mLatencyTimer;
     int mLatencyScore;
 
     uint16_t mSequence;
@@ -159,12 +164,13 @@
     mInterval = mSampleCount / mSampleRate;
 
     // Allocate jitter buffer.
-    for (mBufferMask = 8192; mBufferMask < sampleRate; mBufferMask <<= 1);
-    mBufferMask >>= 2;
+    for (mBufferMask = 8; mBufferMask < mSampleRate; mBufferMask <<= 1);
+    mBufferMask *= BUFFER_SIZE;
     mBuffer = new int16_t[mBufferMask];
     --mBufferMask;
     mBufferHead = 0;
     mBufferTail = 0;
+    mLatencyTimer = 0;
     mLatencyScore = 0;
 
     // Initialize random bits.
@@ -196,8 +202,8 @@
         }
     }
 
-    LOGD("stream[%d] is configured as %s %dkHz %dms", mSocket,
-        (codec ? codec->name : "RAW"), mSampleRate, mInterval);
+    LOGD("stream[%d] is configured as %s %dkHz %dms mode %d", mSocket,
+        (codec ? codec->name : "RAW"), mSampleRate, mInterval, mMode);
     return true;
 }
 
@@ -247,7 +253,7 @@
         mTick += skipped * mInterval;
         mSequence += skipped;
         mTimestamp += skipped * mSampleCount;
-        LOGD("stream[%d] skips %d packets", mSocket, skipped);
+        LOGV("stream[%d] skips %d packets", mSocket, skipped);
     }
 
     tick = mTick;
@@ -296,7 +302,7 @@
     if (!mixed) {
         if ((mTick ^ mLogThrottle) >> 10) {
             mLogThrottle = mTick;
-            LOGD("stream[%d] no data", mSocket);
+            LOGV("stream[%d] no data", mSocket);
         }
         return;
     }
@@ -324,7 +330,7 @@
     buffer[2] = mSsrc;
     int length = mCodec->encode(&buffer[3], samples);
     if (length <= 0) {
-        LOGD("stream[%d] encoder error", mSocket);
+        LOGV("stream[%d] encoder error", mSocket);
         return;
     }
     sendto(mSocket, buffer, length + 12, MSG_DONTWAIT, (sockaddr *)&mRemote,
@@ -340,31 +346,37 @@
     }
 
     // Make sure mBufferHead and mBufferTail are reasonable.
-    if ((unsigned int)(tick + 256 - mBufferHead) > 1024) {
-        mBufferHead = tick - 64;
+    if ((unsigned int)(tick + BUFFER_SIZE - mBufferHead) > BUFFER_SIZE * 2) {
+        mBufferHead = tick - HISTORY_SIZE;
         mBufferTail = mBufferHead;
     }
 
-    if (tick - mBufferHead > 64) {
+    if (tick - mBufferHead > HISTORY_SIZE) {
         // Throw away outdated samples.
-        mBufferHead = tick - 64;
+        mBufferHead = tick - HISTORY_SIZE;
         if (mBufferTail - mBufferHead < 0) {
             mBufferTail = mBufferHead;
         }
     }
 
-    if (mBufferTail - tick <= 80) {
-        mLatencyScore = tick;
-    } else if (tick - mLatencyScore >= 5000) {
-        // Reset the jitter buffer to 40ms if the latency keeps larger than 80ms
-        // in the past 5s. This rarely happens, so let us just keep it simple.
-        LOGD("stream[%d] latency control", mSocket);
-        mBufferTail = tick + 40;
+    // Adjust the jitter buffer if the latency keeps larger than two times of the
+    // packet interval in the past two seconds.
+    int score = mBufferTail - tick - mInterval * 2;
+    if (mLatencyScore > score) {
+        mLatencyScore = score;
+    }
+    if (mLatencyScore <= 0) {
+        mLatencyTimer = tick;
+        mLatencyScore = score;
+    } else if (tick - mLatencyTimer >= MEASURE_PERIOD) {
+        LOGV("stream[%d] reduces latency of %dms", mSocket, mLatencyScore);
+        mBufferTail -= mLatencyScore;
+        mLatencyTimer = tick;
     }
 
-    if (mBufferTail - mBufferHead > 256 - mInterval) {
+    if (mBufferTail - mBufferHead > BUFFER_SIZE - mInterval) {
         // Buffer overflow. Drop the packet.
-        LOGD("stream[%d] buffer overflow", mSocket);
+        LOGV("stream[%d] buffer overflow", mSocket);
         recv(mSocket, &c, 1, MSG_DONTWAIT);
         return;
     }
@@ -388,7 +400,7 @@
         // reliable but at least they can be used to identify duplicates?
         if (length < 12 || length > (int)sizeof(buffer) ||
             (ntohl(*(uint32_t *)buffer) & 0xC07F0000) != mCodecMagic) {
-            LOGD("stream[%d] malformed packet", mSocket);
+            LOGV("stream[%d] malformed packet", mSocket);
             return;
         }
         int offset = 12 + ((buffer[0] & 0x0F) << 2);
@@ -408,22 +420,22 @@
         }
     }
     if (length <= 0) {
-        LOGD("stream[%d] decoder error", mSocket);
+        LOGV("stream[%d] decoder error", mSocket);
         return;
     }
 
     if (tick - mBufferTail > 0) {
-        // Buffer underrun. Reset the jitter buffer to 40ms.
-        LOGD("stream[%d] buffer underrun", mSocket);
+        // Buffer underrun. Reset the jitter buffer.
+        LOGV("stream[%d] buffer underrun", mSocket);
         if (mBufferTail - mBufferHead <= 0) {
-            mBufferHead = tick + 40;
+            mBufferHead = tick + mInterval;
             mBufferTail = mBufferHead;
         } else {
-            int tail = (tick + 40) * mSampleRate;
+            int tail = (tick + mInterval) * mSampleRate;
             for (int i = mBufferTail * mSampleRate; i - tail < 0; ++i) {
                 mBuffer[i & mBufferMask] = 0;
             }
-            mBufferTail = tick + 40;
+            mBufferTail = tick + mInterval;
         }
     }
 
@@ -450,7 +462,6 @@
     bool add(AudioStream *stream);
     bool remove(int socket);
 
-private:
     enum {
         ON_HOLD = 0,
         MUTED = 1,
@@ -459,6 +470,7 @@
         LAST_MODE = 3,
     };
 
+private:
     AudioStream *mChain;
     int mEventQueue;
     volatile int mDtmfEvent;
@@ -680,7 +692,7 @@
     int count = 0;
 
     for (AudioStream *stream = chain; stream; stream = stream->mNext) {
-        if (!stream->mTick || tick - stream->mTick >= 0) {
+        if (tick - stream->mTick >= 0) {
             stream->encode(tick, chain);
         }
         if (deadline - stream->mTick > 0) {
@@ -764,11 +776,14 @@
     char c;
     while (recv(deviceSocket, &c, 1, MSG_DONTWAIT) == 1);
 
-    // Start your engine!
-    track.start();
+    // Start AudioRecord before AudioTrack. This prevents AudioTrack from being
+    // disabled due to buffer underrun while waiting for AudioRecord.
     if (mode != MUTED) {
         record.start();
+        int16_t one;
+        record.read(&one, sizeof(one));
     }
+    track.start();
 
     while (!exitPending()) {
         int16_t output[sampleCount];
@@ -794,34 +809,30 @@
                     track.releaseBuffer(&buffer);
                 } else if (status != TIMED_OUT && status != WOULD_BLOCK) {
                     LOGE("cannot write to AudioTrack");
-                    break;
+                    return true;
                 }
             }
 
             if (toRead > 0) {
                 AudioRecord::Buffer buffer;
-                buffer.frameCount = record.frameCount();
+                buffer.frameCount = toRead;
 
                 status_t status = record.obtainBuffer(&buffer, 1);
                 if (status == NO_ERROR) {
-                    int count = ((int)buffer.frameCount < toRead) ?
-                            buffer.frameCount : toRead;
-                    memcpy(&input[sampleCount - toRead], buffer.i8, count * 2);
-                    toRead -= count;
-                    if (buffer.frameCount < record.frameCount()) {
-                        buffer.frameCount = count;
-                    }
+                    int offset = sampleCount - toRead;
+                    memcpy(&input[offset], buffer.i8, buffer.size);
+                    toRead -= buffer.frameCount;
                     record.releaseBuffer(&buffer);
                 } else if (status != TIMED_OUT && status != WOULD_BLOCK) {
                     LOGE("cannot read from AudioRecord");
-                    break;
+                    return true;
                 }
             }
         }
 
         if (chances <= 0) {
-            LOGE("device loop timeout");
-            break;
+            LOGW("device loop timeout");
+            while (recv(deviceSocket, &c, 1, MSG_DONTWAIT) == 1);
         }
 
         if (mode != MUTED) {
@@ -934,6 +945,10 @@
 
 void setMode(JNIEnv *env, jobject thiz, jint mode)
 {
+    if (mode < 0 || mode > AudioGroup::LAST_MODE) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return;
+    }
     AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative);
     if (group && !group->setMode(mode)) {
         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
diff --git a/voip/jni/rtp/G711Codec.cpp b/voip/jni/rtp/G711Codec.cpp
index 091afa9..a467acf 100644
--- a/voip/jni/rtp/G711Codec.cpp
+++ b/voip/jni/rtp/G711Codec.cpp
@@ -18,7 +18,7 @@
 
 namespace {
 
-int8_t gExponents[128] = {
+const int8_t gExponents[128] = {
     0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
     5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
     6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
diff --git a/voip/jni/rtp/RtpStream.cpp b/voip/jni/rtp/RtpStream.cpp
index 33b88e43..f5efc17 100644
--- a/voip/jni/rtp/RtpStream.cpp
+++ b/voip/jni/rtp/RtpStream.cpp
@@ -88,13 +88,11 @@
 
 jint dup(JNIEnv *env, jobject thiz)
 {
-    int socket1 = env->GetIntField(thiz, gNative);
-    int socket2 = ::dup(socket1);
-    if (socket2 == -1) {
+    int socket = ::dup(env->GetIntField(thiz, gNative));
+    if (socket == -1) {
         jniThrowException(env, "java/lang/IllegalStateException", strerror(errno));
     }
-    LOGD("dup %d to %d", socket1, socket2);
-    return socket2;
+    return socket;
 }
 
 void close(JNIEnv *env, jobject thiz)
@@ -102,7 +100,6 @@
     int socket = env->GetIntField(thiz, gNative);
     ::close(socket);
     env->SetIntField(thiz, gNative, -1);
-    LOGD("close %d", socket);
 }
 
 JNINativeMethod gMethods[] = {