Merge "Added crash-detection mechanism."
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 1b7daa6..f73e4d5 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -68,6 +68,14 @@
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libreverbtest_intermediates)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/soundfx/)
 $(call add-clean-step, find . -type f -name "*.rs" -print0 | xargs -0 touch)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libandroid_runtime_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/lib/libandroid_runtime.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/symbols/system/lib/libandroid_runtime.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/libandroid_runtime.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libhwui_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/lib/libhwui.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/symbols/system/lib/libhwui.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/libhwui.so)
 
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/api/9.xml b/api/9.xml
index abb67f9..f151a16 100644
--- a/api/9.xml
+++ b/api/9.xml
@@ -58593,16 +58593,6 @@
 <parameter name="bitmap" type="android.graphics.Bitmap">
 </parameter>
 </constructor>
-<constructor name="Canvas"
- type="android.graphics.Canvas"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="gl" type="javax.microedition.khronos.opengles.GL">
-</parameter>
-</constructor>
 <method name="clipPath"
  return="boolean"
  abstract="false"
@@ -59519,17 +59509,6 @@
 <parameter name="paint" type="android.graphics.Paint">
 </parameter>
 </method>
-<method name="freeGlCaches"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="true"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
 <method name="getClipBounds"
  return="boolean"
  abstract="false"
@@ -59576,17 +59555,6 @@
  visibility="public"
 >
 </method>
-<method name="getGL"
- return="javax.microedition.khronos.opengles.GL"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
 <method name="getHeight"
  return="int"
  abstract="false"
@@ -59950,21 +59918,6 @@
 <parameter name="matrix" type="android.graphics.Matrix">
 </parameter>
 </method>
-<method name="setViewport"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="width" type="int">
-</parameter>
-<parameter name="height" type="int">
-</parameter>
-</method>
 <method name="skew"
  return="void"
  abstract="false"
diff --git a/api/current.xml b/api/current.xml
index 767f9af..61c1cac 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -38871,7 +38871,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -39106,6 +39106,19 @@
 <parameter name="uri" type="android.net.Uri">
 </parameter>
 </constructor>
+<method name="coerceToText"
+ return="java.lang.CharSequence"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</method>
 <method name="getIntent"
  return="android.content.Intent"
  abstract="false"
@@ -39463,6 +39476,21 @@
 <parameter name="values" type="android.content.ContentValues[]">
 </parameter>
 </method>
+<method name="compareMimeTypes"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="concreteType" type="java.lang.String">
+</parameter>
+<parameter name="desiredType" type="java.lang.String">
+</parameter>
+</method>
 <method name="delete"
  return="int"
  abstract="true"
@@ -39513,6 +39541,21 @@
  visibility="public"
 >
 </method>
+<method name="getStreamTypes"
+ return="java.lang.String[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="mimeTypeFilter" type="java.lang.String">
+</parameter>
+</method>
 <method name="getType"
  return="java.lang.String"
  abstract="true"
@@ -39649,6 +39692,48 @@
 <exception name="FileNotFoundException" type="java.io.FileNotFoundException">
 </exception>
 </method>
+<method name="openPipeHelper"
+ return="android.os.ParcelFileDescriptor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="mimeType" type="java.lang.String">
+</parameter>
+<parameter name="opts" type="android.os.Bundle">
+</parameter>
+<parameter name="args" type="T">
+</parameter>
+<parameter name="func" type="android.content.ContentProvider.PipeDataWriter&lt;T&gt;">
+</parameter>
+<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
+</exception>
+</method>
+<method name="openTypedAssetFile"
+ return="android.content.res.AssetFileDescriptor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="mimeTypeFilter" type="java.lang.String">
+</parameter>
+<parameter name="opts" type="android.os.Bundle">
+</parameter>
+<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
+</exception>
+</method>
 <method name="query"
  return="android.database.Cursor"
  abstract="true"
@@ -39740,6 +39825,35 @@
 </parameter>
 </method>
 </class>
+<interface name="ContentProvider.PipeDataWriter"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="writeDataToPipe"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="output" type="android.os.ParcelFileDescriptor">
+</parameter>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="mimeType" type="java.lang.String">
+</parameter>
+<parameter name="opts" type="android.os.Bundle">
+</parameter>
+<parameter name="args" type="T">
+</parameter>
+</method>
+</interface>
 <class name="ContentProviderClient"
  extends="java.lang.Object"
  abstract="false"
@@ -39812,6 +39926,23 @@
  visibility="public"
 >
 </method>
+<method name="getStreamTypes"
+ return="java.lang.String[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="url" type="android.net.Uri">
+</parameter>
+<parameter name="mimeTypeFilter" type="java.lang.String">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
 <method name="getType"
  return="java.lang.String"
  abstract="false"
@@ -39882,6 +40013,27 @@
 <exception name="RemoteException" type="android.os.RemoteException">
 </exception>
 </method>
+<method name="openTypedAssetFileDescriptor"
+ return="android.content.res.AssetFileDescriptor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="mimeType" type="java.lang.String">
+</parameter>
+<parameter name="opts" type="android.os.Bundle">
+</parameter>
+<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
+</exception>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
 <method name="query"
  return="android.database.Cursor"
  abstract="false"
@@ -40652,6 +40804,21 @@
 <parameter name="authority" type="java.lang.String">
 </parameter>
 </method>
+<method name="getStreamTypes"
+ return="java.lang.String[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="url" type="android.net.Uri">
+</parameter>
+<parameter name="mimeTypeFilter" type="java.lang.String">
+</parameter>
+</method>
 <method name="getSyncAdapterTypes"
  return="android.content.SyncAdapterType[]"
  abstract="false"
@@ -40849,6 +41016,25 @@
 <exception name="FileNotFoundException" type="java.io.FileNotFoundException">
 </exception>
 </method>
+<method name="openTypedAssetFileDescriptor"
+ return="android.content.res.AssetFileDescriptor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="mimeType" type="java.lang.String">
+</parameter>
+<parameter name="opts" type="android.os.Bundle">
+</parameter>
+<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
+</exception>
+</method>
 <method name="query"
  return="android.database.Cursor"
  abstract="false"
@@ -47262,6 +47448,17 @@
  visibility="public"
 >
 </field>
+<field name="ACTION_PASTE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.intent.action.PASTE&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="ACTION_PICK"
  type="java.lang.String"
  transient="false"
@@ -68809,16 +69006,6 @@
 <parameter name="bitmap" type="android.graphics.Bitmap">
 </parameter>
 </constructor>
-<constructor name="Canvas"
- type="android.graphics.Canvas"
- static="false"
- final="false"
- deprecated="deprecated"
- visibility="public"
->
-<parameter name="gl" type="javax.microedition.khronos.opengles.GL">
-</parameter>
-</constructor>
 <method name="clipPath"
  return="boolean"
  abstract="false"
@@ -69735,17 +69922,6 @@
 <parameter name="paint" type="android.graphics.Paint">
 </parameter>
 </method>
-<method name="freeGlCaches"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="true"
- final="false"
- deprecated="deprecated"
- visibility="public"
->
-</method>
 <method name="getClipBounds"
  return="boolean"
  abstract="false"
@@ -69800,7 +69976,7 @@
  static="false"
  final="false"
  deprecated="deprecated"
- visibility="public"
+ visibility="protected"
 >
 </method>
 <method name="getHeight"
@@ -70177,21 +70353,6 @@
 <parameter name="matrix" type="android.graphics.Matrix">
 </parameter>
 </method>
-<method name="setViewport"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="deprecated"
- visibility="public"
->
-<parameter name="width" type="int">
-</parameter>
-<parameter name="height" type="int">
-</parameter>
-</method>
 <method name="skew"
  return="void"
  abstract="false"
@@ -102917,19 +103078,6 @@
 <parameter name="value" type="java.lang.String">
 </parameter>
 </method>
-<method name="setShowNotification"
- return="android.net.DownloadManager.Request"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="flags" type="int">
-</parameter>
-</method>
 <method name="setTitle"
  return="android.net.DownloadManager.Request"
  abstract="false"
@@ -102976,17 +103124,6 @@
  visibility="public"
 >
 </field>
-<field name="NOTIFICATION_WHEN_RUNNING"
- type="int"
- transient="false"
- volatile="false"
- value="1"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
 </class>
 <class name="LocalServerSocket"
  extends="java.lang.Object"
@@ -133993,6 +134130,19 @@
 <exception name="IOException" type="java.io.IOException">
 </exception>
 </method>
+<method name="createPipe"
+ return="android.os.ParcelFileDescriptor[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
 <method name="describeContents"
  return="int"
  abstract="false"
@@ -180176,6 +180326,483 @@
 </parameter>
 </method>
 </class>
+<class name="JsonReader"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="java.io.Closeable">
+</implements>
+<constructor name="JsonReader"
+ type="android.util.JsonReader"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="in" type="java.io.Reader">
+</parameter>
+</constructor>
+<method name="beginArray"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="beginObject"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="close"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="endArray"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="endObject"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="hasNext"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextBoolean"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextDouble"
+ return="double"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextInt"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextLong"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextName"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextNull"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextString"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="peek"
+ return="android.util.JsonToken"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="setLenient"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="lenient" type="boolean">
+</parameter>
+</method>
+<method name="skipValue"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="syntaxError"
+ return="java.io.IOException"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="message" type="java.lang.String">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+</class>
+<class name="JsonToken"
+ extends="java.lang.Enum"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="valueOf"
+ return="android.util.JsonToken"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="name" type="java.lang.String">
+</parameter>
+</method>
+<method name="values"
+ return="android.util.JsonToken[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
+<class name="JsonWriter"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="java.io.Closeable">
+</implements>
+<constructor name="JsonWriter"
+ type="android.util.JsonWriter"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="out" type="java.io.Writer">
+</parameter>
+</constructor>
+<method name="beginArray"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="beginObject"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="close"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="endArray"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="endObject"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="flush"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="name"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="name" type="java.lang.String">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nullValue"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="setIndent"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="indent" type="java.lang.String">
+</parameter>
+</method>
+<method name="value"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="java.lang.String">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="value"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="boolean">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="value"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="double">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="value"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="long">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+</class>
 <class name="Log"
  extends="java.lang.Object"
  abstract="false"
@@ -212598,6 +213225,28 @@
  visibility="public"
 >
 </method>
+<method name="getVisibleTitleHeight"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVisibleTitleHeight"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getZoomControls"
  return="android.view.View"
  abstract="false"
diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c
index 1fde437..02c9cbc 100644
--- a/cmds/dumpstate/dumpstate.c
+++ b/cmds/dumpstate/dumpstate.c
@@ -167,8 +167,11 @@
 }
 
 static void usage() {
-    fprintf(stderr, "usage: dumpstate [-d] [-o file] [-s] [-z]\n"
+    fprintf(stderr, "usage: dumpstate [-b file] [-d] [-e file] [-o file] [-s] "
+            "[-z]\n"
+            "  -b: play sound file instead of vibrate, at beginning of job\n"
             "  -d: append date to filename (requires -o)\n"
+            "  -e: play sound file instead of vibrate, at end of job\n"
             "  -o: write to file (instead of stdout)\n"
             "  -s: write output to control socket (for init)\n"
             "  -z: gzip output (requires -o)\n");
@@ -178,6 +181,8 @@
     int do_add_date = 0;
     int do_compress = 0;
     char* use_outfile = 0;
+    char* begin_sound = 0;
+    char* end_sound = 0;
     int use_socket = 0;
 
     LOGI("begin\n");
@@ -194,9 +199,11 @@
     dump_traces_path = dump_vm_traces();
 
     int c;
-    while ((c = getopt(argc, argv, "dho:svz")) != -1) {
+    while ((c = getopt(argc, argv, "b:de:ho:svz")) != -1) {
         switch (c) {
+            case 'b': begin_sound = optarg;  break;
             case 'd': do_add_date = 1;       break;
+            case 'e': end_sound = optarg;    break;
             case 'o': use_outfile = optarg;  break;
             case 's': use_socket = 1;        break;
             case 'v': break;  // compatibility no-op
@@ -244,16 +251,18 @@
         gzip_pid = redirect_to_file(stdout, tmp_path, do_compress);
     }
 
-    /* bzzzzzz */
-    if (vibrator) {
+    if (begin_sound) {
+        play_sound(begin_sound);
+    } else if (vibrator) {
         fputs("150", vibrator);
         fflush(vibrator);
     }
 
     dumpstate();
 
-    /* bzzz bzzz bzzz */
-    if (vibrator) {
+    if (end_sound) {
+        play_sound(end_sound);
+    } else if (vibrator) {
         int i;
         for (i = 0; i < 3; i++) {
             fputs("75\n", vibrator);
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 682eafd..83b1d11 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -44,4 +44,7 @@
 /* Displays a blocked processes in-kernel wait channel */
 void show_wchan(int pid, const char *name);
 
+/* Play a sound via Stagefright */
+void play_sound(const char* path);
+
 #endif /* _DUMPSTATE_H_ */
diff --git a/cmds/dumpstate/utils.c b/cmds/dumpstate/utils.c
index c7a78cc..f92acbbb 100644
--- a/cmds/dumpstate/utils.c
+++ b/cmds/dumpstate/utils.c
@@ -429,3 +429,7 @@
     rename(anr_traces_path, traces_path);
     return dump_traces_path;
 }
+
+void play_sound(const char* path) {
+    run_command(NULL, 5, "/system/bin/stagefright", "-o", "-a", path, NULL);
+}
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index b0adaec..fd3a0d0 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -45,26 +45,26 @@
 
 /**
  * This class provides access to a centralized registry of the user's
- * online accounts.  With this service, users only need to enter their
- * credentials (username and password) once for any account, granting
- * applications access to online resources with "one-click" approval.
+ * online accounts.  The user enters credentials (username and password) once
+ * per account, granting applications access to online resources with
+ * "one-click" approval.
  *
  * <p>Different online services have different ways of handling accounts and
  * authentication, so the account manager uses pluggable <em>authenticator</em>
- * modules for different <em>account types</em>.  The authenticators (which
- * may be written by third parties) handle the actual details of validating
- * account credentials and storing account information.  For example, Google,
- * Facebook, and Microsoft Exchange each have their own authenticator.
+ * modules for different <em>account types</em>.  Authenticators (which may be
+ * written by third parties) handle the actual details of validating account
+ * credentials and storing account information.  For example, Google, Facebook,
+ * and Microsoft Exchange each have their own authenticator.
  *
  * <p>Many servers support some notion of an <em>authentication token</em>,
  * which can be used to authenticate a request to the server without sending
  * the user's actual password.  (Auth tokens are normally created with a
  * separate request which does include the user's credentials.)  AccountManager
- * can generate these auth tokens for applications, so the application doesn't
- * need to handle passwords directly.  Auth tokens are normally reusable, and
- * cached by AccountManager, but must be refreshed periodically.  It's the
- * responsibility of applications to <em>invalidate</em> auth tokens when they
- * stop working so the AccountManager knows it needs to regenerate them.
+ * can generate auth tokens for applications, so the application doesn't need to
+ * handle passwords directly.  Auth tokens are normally reusable and cached by
+ * AccountManager, but must be refreshed periodically.  It's the responsibility
+ * of applications to <em>invalidate</em> auth tokens when they stop working so
+ * the AccountManager knows it needs to regenerate them.
  *
  * <p>Applications accessing a server normally go through these steps:
  *
@@ -84,14 +84,19 @@
  * {@link #addAccount} may be called to prompt the user to create an
  * account of the appropriate type.
  *
+ * <li><b>Important:</b> If the application is using a previously remembered
+ * account selection, it must make sure the account is still in the list
+ * of accounts returned by {@link #getAccountsByType}.  Requesting an auth token
+ * for an account no longer on the device results in an undefined failure.
+ *
  * <li>Request an auth token for the selected account(s) using one of the
  * {@link #getAuthToken} methods or related helpers.  Refer to the description
  * of each method for exact usage and error handling details.
  *
  * <li>Make the request using the auth token.  The form of the auth token,
  * the format of the request, and the protocol used are all specific to the
- * service you are accessing.  The application makes the request itself, using
- * whatever network and protocol libraries are useful.
+ * service you are accessing.  The application may use whatever network and
+ * protocol libraries are useful.
  *
  * <li><b>Important:</b> If the request fails with an authentication error,
  * it could be that a cached auth token is stale and no longer honored by
@@ -103,7 +108,7 @@
  * appropriate actions taken.
  * </ul>
  *
- * <p>Some AccountManager methods may require interaction with the user to
+ * <p>Some AccountManager methods may need to interact with the user to
  * prompt for credentials, present options, or ask the user to add an account.
  * The caller may choose whether to allow AccountManager to directly launch the
  * necessary user interface and wait for the user, or to return an Intent which
@@ -113,18 +118,17 @@
  * the current foreground {@link Activity} context.
  *
  * <p>Many AccountManager methods take {@link AccountManagerCallback} and
- * {@link Handler} as parameters.  These methods return immediately but
+ * {@link Handler} as parameters.  These methods return immediately and
  * run asynchronously. If a callback is provided then
  * {@link AccountManagerCallback#run} will be invoked on the Handler's
  * thread when the request completes, successfully or not.
- * An {@link AccountManagerFuture} is returned by these requests and also
- * supplied to the callback (if any).  The result is retrieved by calling
- * {@link AccountManagerFuture#getResult()} which waits for the operation
- * to complete (if necessary) and either returns the result or throws an
- * exception if an error occurred during the operation.
- * To make the request synchronously, call
+ * The result is retrieved by calling {@link AccountManagerFuture#getResult()}
+ * on the {@link AccountManagerFuture} returned by the method (and also passed
+ * to the callback).  This method waits for the operation to complete (if
+ * necessary) and either returns the result or throws an exception if an error
+ * occurred during the operation.  To make the request synchronously, call
  * {@link AccountManagerFuture#getResult()} immediately on receiving the
- * future from the method.  No callback need be supplied.
+ * future from the method; no callback need be supplied.
  *
  * <p>Requests which may block, including
  * {@link AccountManagerFuture#getResult()}, must never be called on
@@ -143,32 +147,32 @@
     public static final int ERROR_CODE_BAD_REQUEST = 8;
 
     /**
-     * The Bundle key used for the {@link String} account name in results
+     * Bundle key used for the {@link String} account name in results
      * from methods which return information about a particular account.
      */
     public static final String KEY_ACCOUNT_NAME = "authAccount";
 
     /**
-     * The Bundle key used for the {@link String} account type in results
+     * Bundle key used for the {@link String} account type in results
      * from methods which return information about a particular account.
      */
     public static final String KEY_ACCOUNT_TYPE = "accountType";
 
     /**
-     * The Bundle key used for the auth token value in results
+     * Bundle key used for the auth token value in results
      * from {@link #getAuthToken} and friends.
      */
     public static final String KEY_AUTHTOKEN = "authtoken";
 
     /**
-     * The Bundle key used for an {@link Intent} in results from methods that
+     * Bundle key used for an {@link Intent} in results from methods that
      * may require the caller to interact with the user.  The Intent can
      * be used to start the corresponding user interface activity.
      */
     public static final String KEY_INTENT = "intent";
 
     /**
-     * The Bundle key used to supply the password directly in options to
+     * Bundle key used to supply the password directly in options to
      * {@link #confirmCredentials}, rather than prompting the user with
      * the standard password prompt.
      */
@@ -476,7 +480,7 @@
      * @param account The {@link Account} to add
      * @param password The password to associate with the account, null for none
      * @param userdata String values to use for the account's userdata, null for none
-     * @return Whether the account was successfully added.  False if the account
+     * @return True if the account was successfully added, false if the account
      *     already exists, the account is null, or another error occurs.
      */
     public boolean addAccountExplicitly(Account account, String password, Bundle userdata) {
@@ -733,15 +737,14 @@
      * sense to ask the user directly for a password.
      *
      * <p>If a previously generated auth token is cached for this account and
-     * type, then it will be returned.  Otherwise, if we have a saved password
-     * the server accepts, it will be used to generate a new auth token.
-     * Otherwise, the user will be asked for a password, which will be sent to
-     * the server to generate a new auth token.
+     * type, then it is returned.  Otherwise, if a saved password is
+     * available, it is sent to the server to generate a new auth token.
+     * Otherwise, the user is prompted to enter a password.
      *
-     * <p>The value of the auth token type depends on the authenticator.
-     * Some services use different tokens to access different functionality --
-     * for example, Google uses different auth tokens to access Gmail and
-     * Google Calendar for the same account.
+     * <p>Some authenticators have auth token <em>types</em>, whose value
+     * is authenticator-dependent.  Some services use different token types to
+     * access different functionality -- for example, Google uses different auth
+     * tokens to access Gmail and Google Calendar for the same account.
      *
      * <p>This method may be called from any thread, but the returned
      * {@link AccountManagerFuture} must not be used on the main thread.
@@ -778,6 +781,9 @@
      * <li> {@link IOException} if the authenticator experienced an I/O problem
      *      creating a new auth token, usually because of network trouble
      * </ul>
+     * If the account is no longer present on the device, the return value is
+     * authenticator-dependent.  The caller should verify the validity of the
+     * account before requesting an auth token.
      */
     public AccountManagerFuture<Bundle> getAuthToken(
             final Account account, final String authTokenType, final Bundle options,
@@ -800,29 +806,27 @@
      * user should not be immediately interrupted with a password prompt.
      *
      * <p>If a previously generated auth token is cached for this account and
-     * type, then it will be returned.  Otherwise, if we have saved credentials
-     * the server accepts, it will be used to generate a new auth token.
-     * Otherwise, an Intent will be returned which, when started, will prompt
-     * the user for a password.  If the notifyAuthFailure parameter is set,
-     * the same Intent will be associated with a status bar notification,
+     * type, then it is returned.  Otherwise, if a saved password is
+     * available, it is sent to the server to generate a new auth token.
+     * Otherwise, an {@link Intent} is returned which, when started, will
+     * prompt the user for a password.  If the notifyAuthFailure parameter is
+     * set, a status bar notification is also created with the same Intent,
      * alerting the user that they need to enter a password at some point.
      *
-     * <p>If the intent is left in a notification, you will need to wait until
-     * the user gets around to entering a password before trying again,
-     * which could be hours or days or never.  When it does happen, the
-     * account manager will broadcast the {@link #LOGIN_ACCOUNTS_CHANGED_ACTION}
-     * {@link Intent}, which applications can use to trigger another attempt
-     * to fetch an auth token.
+     * <p>In that case, you may need to wait until the user responds, which
+     * could take hours or days or forever.  When the user does respond and
+     * supply a new password, the account manager will broadcast the
+     * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent, which applications can
+     * use to try again.
      *
-     * <p>If notifications are not enabled, it is the application's
-     * responsibility to launch the returned intent at some point to let
-     * the user enter credentials.  In either case, the result from this
-     * call will not wait for user action.
+     * <p>If notifyAuthFailure is not set, it is the application's
+     * responsibility to launch the returned Intent at some point.
+     * Either way, the result from this call will not wait for user action.
      *
-     * <p>The value of the auth token type depends on the authenticator.
-     * Some services use different tokens to access different functionality --
-     * for example, Google uses different auth tokens to access Gmail and
-     * Google Calendar for the same account.
+     * <p>Some authenticators have auth token <em>types</em>, whose value
+     * is authenticator-dependent.  Some services use different token types to
+     * access different functionality -- for example, Google uses different auth
+     * tokens to access Gmail and Google Calendar for the same account.
      *
      * <p>This method may be called from any thread, but the returned
      * {@link AccountManagerFuture} must not be used on the main thread.
@@ -851,7 +855,7 @@
      * must enter credentials, the returned Bundle contains only
      * {@link #KEY_INTENT} with the {@link Intent} needed to launch a prompt.
      *
-     * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+     * If an error occurred, {@link AccountManagerFuture#getResult()} throws:
      * <ul>
      * <li> {@link AuthenticatorException} if the authenticator failed to respond
      * <li> {@link OperationCanceledException} if the operation is canceled for
@@ -859,6 +863,9 @@
      * <li> {@link IOException} if the authenticator experienced an I/O problem
      *      creating a new auth token, usually because of network trouble
      * </ul>
+     * If the account is no longer present on the device, the return value is
+     * authenticator-dependent.  The caller should verify the validity of the
+     * account before requesting an auth token.
      */
     public AccountManagerFuture<Bundle> getAuthToken(
             final Account account, final String authTokenType, final boolean notifyAuthFailure,
@@ -910,9 +917,8 @@
      *
      * If no activity was specified, the returned Bundle contains only
      * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
-     * actual account creation process.
-     *
-     * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+     * actual account creation process.  If an error occurred,
+     * {@link AccountManagerFuture#getResult()} throws:
      * <ul>
      * <li> {@link AuthenticatorException} if no authenticator was registered for
      *      this account type or the authenticator failed to respond
@@ -979,9 +985,8 @@
      *
      * If no activity or password was specified, the returned Bundle contains
      * only {@link #KEY_INTENT} with the {@link Intent} needed to launch the
-     * password prompt.
-     *
-     * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+     * password prompt.  If an error occurred,
+     * {@link AccountManagerFuture#getResult()} throws:
      * <ul>
      * <li> {@link AuthenticatorException} if the authenticator failed to respond
      * <li> {@link OperationCanceledException} if the operation was canceled for
@@ -1040,9 +1045,8 @@
      *
      * If no activity was specified, the returned Bundle contains only
      * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
-     * password prompt.
-     *
-     * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+     * password prompt.  If an error occurred,
+     * {@link AccountManagerFuture#getResult()} throws:
      * <ul>
      * <li> {@link AuthenticatorException} if the authenticator failed to respond
      * <li> {@link OperationCanceledException} if the operation was canceled for
@@ -1091,8 +1095,8 @@
      *     which is empty if properties were edited successfully, or
      *     if no activity was specified, contains only {@link #KEY_INTENT}
      *     needed to launch the authenticator's settings dialog.
-     *
-     * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+     *     If an error occurred, {@link AccountManagerFuture#getResult()}
+     *     throws:
      * <ul>
      * <li> {@link AuthenticatorException} if no authenticator was registered for
      *      this account type or the authenticator failed to respond
@@ -1617,7 +1621,7 @@
      * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
      * </ul>
      *
-     * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+     * If an error occurred, {@link AccountManagerFuture#getResult()} throws:
      * <ul>
      * <li> {@link AuthenticatorException} if no authenticator was registered for
      *      this account type or the authenticator failed to respond
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index c55c07f..d49adc2 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -969,10 +969,6 @@
             mTitleReady = true;
             onTitleChanged(getTitle(), getTitleColor());
         }
-        if (mWindow != null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
-            // Invalidate the action bar menu so that it can initialize properly. 
-            mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
-        }
         mCalled = true;
     }
 
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index 5371fa5..d685cf3 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -34,6 +34,12 @@
  * You do not instantiate this class directly; instead, retrieve it through
  * {@link android.content.Context#getSystemService}.
  *
+ * <p>
+ * The ClipboardManager API itself is very simple: it consists of methods
+ * to atomically get and set the current primary clipboard data.  That data
+ * is expressed as a {@link ClippedData} object, which defines the protocol
+ * for data exchange between applications.
+ *
  * @see android.content.Context#getSystemService
  */
 public class ClipboardManager extends android.text.ClipboardManager {
@@ -152,7 +158,7 @@
     public CharSequence getText() {
         ClippedData clip = getPrimaryClip();
         if (clip != null && clip.getItemCount() > 0) {
-            return clip.getItem(0).getText();
+            return clip.getItem(0).coerceToText(mContext);
         }
         return null;
     }
@@ -167,11 +173,11 @@
     }
 
     /**
-     * Returns true if the clipboard has a primary clip containing text; false otherwise.
+     * @deprecated Use {@link #hasPrimaryClip()} instead.
      */
     public boolean hasText() {
         try {
-            return getService().hasClipboardText();
+            return getService().hasPrimaryClip();
         } catch (RemoteException e) {
             return false;
         }
diff --git a/core/java/android/content/ClippedData.java b/core/java/android/content/ClippedData.java
index ebb194f..c3f0237 100644
--- a/core/java/android/content/ClippedData.java
+++ b/core/java/android/content/ClippedData.java
@@ -16,12 +16,18 @@
 
 package android.content;
 
+import android.content.res.AssetFileDescriptor;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.util.Log;
 
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
 import java.util.ArrayList;
 
 /**
@@ -31,19 +37,88 @@
  * each of which can hold one or more representations of an item of data.
  * For display to the user, it also has a label and iconic representation.</p>
  *
- * <p>The types than an individial item can currently contain are:</p>
+ * <p>Each Item instance can be one of three main classes of data: a simple
+ * CharSequence of text, a single Intent object, or a Uri.  See {@link Item}
+ * for more details.
  *
- * <ul>
- * <li> Text: a basic string of text.  This is actually a CharSequence,
- * so it can be formatted text supported by corresponding Android built-in
- * style spans.  (Custom application spans are not supported and will be
- * stripped when transporting through the clipboard.)
- * <li> Intent: an arbitrary Intent object.  A typical use is the shortcut
- * to create when pasting a clipped item on to the home screen.
- * <li> Uri: a URI reference.  Currently this should only be a content: URI.
- * This representation allows an application to share complex or large clips,
- * by providing a URI to a content provider holding the data.
- * </ul>
+ * <a name="ImplementingPaste"></a>
+ * <h3>Implementing Paste or Drop</h3>
+ *
+ * <p>To implement a paste or drop of a ClippedData object into an application,
+ * the application must correctly interpret the data for its use.  If the {@link Item}
+ * it contains is simple text or an Intent, there is little to be done: text
+ * can only be interpreted as text, and an Intent will typically be used for
+ * creating shortcuts (such as placing icons on the home screen) or other
+ * actions.
+ *
+ * <p>If all you want is the textual representation of the clipped data, you
+ * can use the convenience method {@link Item#coerceToText Item.coerceToText}.
+ *
+ * <p>More complicated exchanges will be done through URIs, in particular
+ * "content:" URIs.  A content URI allows the recipient of a ClippedData item
+ * to interact closely with the ContentProvider holding the data in order to
+ * negotiate the transfer of that data.
+ *
+ * <p>For example, here is the paste function of a simple NotePad application.
+ * When retrieving the data from the clipboard, it can do either two things:
+ * if the clipboard contains a URI reference to an existing note, it copies
+ * the entire structure of the note into a new note; otherwise, it simply
+ * coerces the clip into text and uses that as the new note's contents.
+ *
+ * {@sample development/samples/NotePad/src/com/example/android/notepad/NoteEditor.java
+ *      paste}
+ *
+ * <p>In many cases an application can paste various types of streams of data.  For
+ * example, an e-mail application may want to allow the user to paste an image
+ * or other binary data as an attachment.  This is accomplished through the
+ * ContentResolver {@link ContentResolver#getStreamTypes(Uri, String)} and
+ * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, android.os.Bundle)}
+ * methods.  These allow a client to discover the type(s) of data that a particular
+ * content URI can make available as a stream and retrieve the stream of data.
+ *
+ * <p>For example, the implementation of {@link Item#coerceToText Item.coerceToText}
+ * itself uses this to try to retrieve a URI clip as a stream of text:
+ *
+ * {@sample frameworks/base/core/java/android/content/ClippedData.java coerceToText}
+ *
+ * <a name="ImplementingCopy"></a>
+ * <h3>Implementing Copy or Drag</h3>
+ *
+ * <p>To be the source of a clip, the application must construct a ClippedData
+ * object that any recipient can interpret best for their context.  If the clip
+ * is to contain a simple text, Intent, or URI, this is easy: an {@link Item}
+ * containing the appropriate data type can be constructed and used.
+ *
+ * <p>More complicated data types require the implementation of support in
+ * a ContentProvider for describing and generating the data for the recipient.
+ * A common scenario is one where an application places on the clipboard the
+ * content: URI of an object that the user has copied, with the data at that
+ * URI consisting of a complicated structure that only other applications with
+ * direct knowledge of the structure can use.
+ *
+ * <p>For applications that do not have intrinsic knowledge of the data structure,
+ * the content provider holding it can make the data available as an arbitrary
+ * number of types of data streams.  This is done by implementing the
+ * ContentProvider {@link ContentProvider#getStreamTypes(Uri, String)} and
+ * {@link ContentProvider#openTypedAssetFile(Uri, String, android.os.Bundle)}
+ * methods.
+ *
+ * <p>Going back to our simple NotePad application, this is the implementation
+ * it may have to convert a single note URI (consisting of a title and the note
+ * text) into a stream of plain text data.
+ *
+ * {@sample development/samples/NotePad/src/com/example/android/notepad/NotePadProvider.java
+ *      stream}
+ *
+ * <p>The copy operation in our NotePad application is now just a simple matter
+ * of making a clip containing the URI of the note being copied:
+ *
+ * {@sample development/samples/NotePad/src/com/example/android/notepad/NotesList.java
+ *      copy}
+ *
+ * <p>Note if a paste operation needs this clip as text (for example to paste
+ * into an editor), then {@link Item#coerceToText(Context)} will ask the content
+ * provider for the clip URI as text and successfully paste the entire note.
  */
 public class ClippedData implements Parcelable {
     CharSequence mLabel;
@@ -51,40 +126,166 @@
 
     final ArrayList<Item> mItems = new ArrayList<Item>();
 
+    /**
+     * Description of a single item in a ClippedData.
+     *
+     * <p>The types than an individual item can currently contain are:</p>
+     *
+     * <ul>
+     * <li> Text: a basic string of text.  This is actually a CharSequence,
+     * so it can be formatted text supported by corresponding Android built-in
+     * style spans.  (Custom application spans are not supported and will be
+     * stripped when transporting through the clipboard.)
+     * <li> Intent: an arbitrary Intent object.  A typical use is the shortcut
+     * to create when pasting a clipped item on to the home screen.
+     * <li> Uri: a URI reference.  This may be any URI (such as an http: URI
+     * representing a bookmark), however it is often a content: URI.  Using
+     * content provider references as clips like this allows an application to
+     * share complex or large clips through the standard content provider
+     * facilities.
+     * </ul>
+     */
     public static class Item {
         CharSequence mText;
         Intent mIntent;
         Uri mUri;
 
+        /**
+         * Create an Item consisting of a single block of (possibly styled) text.
+         */
         public Item(CharSequence text) {
             mText = text;
         }
 
+        /**
+         * Create an Item consisting of an arbitrary Intent.
+         */
         public Item(Intent intent) {
             mIntent = intent;
         }
 
+        /**
+         * Create an Item consisting of an arbitrary URI.
+         */
         public Item(Uri uri) {
             mUri = uri;
         }
 
+        /**
+         * Create a complex Item, containing multiple representations of
+         * text, intent, and/or URI.
+         */
         public Item(CharSequence text, Intent intent, Uri uri) {
             mText = text;
             mIntent = intent;
             mUri = uri;
         }
 
+        /**
+         * Retrieve the raw text contained in this Item.
+         */
         public CharSequence getText() {
             return mText;
         }
 
+        /**
+         * Retrieve the raw Intent contained in this Item.
+         */
         public Intent getIntent() {
             return mIntent;
         }
 
+        /**
+         * Retrieve the raw URI contained in this Item.
+         */
         public Uri getUri() {
             return mUri;
         }
+
+        /**
+         * Turn this item into text, regardless of the type of data it
+         * actually contains.
+         *
+         * <p>The algorithm for deciding what text to return is:
+         * <ul>
+         * <li> If {@link #getText} is non-null, return that.
+         * <li> If {@link #getUri} is non-null, try to retrieve its data
+         * as a text stream from its content provider.  If this succeeds, copy
+         * the text into a String and return it.  If it is not a content: URI or
+         * the content provider does not supply a text representation, return
+         * the raw URI as a string.
+         * <li> If {@link #getIntent} is non-null, convert that to an intent:
+         * URI and returnit.
+         * <li> Otherwise, return an empty string.
+         * </ul>
+         *
+         * @param context The caller's Context, from which its ContentResolver
+         * and other things can be retrieved.
+         * @return Returns the item's textual representation.
+         */
+//BEGIN_INCLUDE(coerceToText)
+        public CharSequence coerceToText(Context context) {
+            // If this Item has an explicit textual value, simply return that.
+            if (mText != null) {
+                return mText;
+            }
+
+            // If this Item has a URI value, try using that.
+            if (mUri != null) {
+
+                // First see if the URI can be opened as a plain text stream
+                // (of any sub-type).  If so, this is the best textual
+                // representation for it.
+                FileInputStream stream = null;
+                try {
+                    // Ask for a stream of the desired type.
+                    AssetFileDescriptor descr = context.getContentResolver()
+                            .openTypedAssetFileDescriptor(mUri, "text/*", null);
+                    stream = descr.createInputStream();
+                    InputStreamReader reader = new InputStreamReader(stream, "UTF-8");
+
+                    // Got it...  copy the stream into a local string and return it.
+                    StringBuilder builder = new StringBuilder(128);
+                    char[] buffer = new char[8192];
+                    int len;
+                    while ((len=reader.read(buffer)) > 0) {
+                        builder.append(buffer, 0, len);
+                    }
+                    return builder.toString();
+
+                } catch (FileNotFoundException e) {
+                    // Unable to open content URI as text...  not really an
+                    // error, just something to ignore.
+
+                } catch (IOException e) {
+                    // Something bad has happened.
+                    Log.w("ClippedData", "Failure loading text", e);
+                    return e.toString();
+
+                } finally {
+                    if (stream != null) {
+                        try {
+                            stream.close();
+                        } catch (IOException e) {
+                        }
+                    }
+                }
+
+                // If we couldn't open the URI as a stream, then the URI itself
+                // probably serves fairly well as a textual representation.
+                return mUri.toString();
+            }
+
+            // Finally, if all we have is an Intent, then we can just turn that
+            // into text.  Not the most user-friendly thing, but it's something.
+            if (mIntent != null) {
+                return mIntent.toUri(Intent.URI_INTENT_SCHEME);
+            }
+
+            // Shouldn't get here, but just in case...
+            return "";
+        }
+//END_INCLUDE(coerceToText)
     }
 
     /**
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index a3252ed..1163add 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -28,6 +28,7 @@
 import android.database.IContentObserver;
 import android.database.SQLException;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
@@ -36,6 +37,7 @@
 
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.util.ArrayList;
 
 /**
@@ -251,6 +253,18 @@
             return ContentProvider.this.call(method, request, args);
         }
 
+        @Override
+        public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
+            return ContentProvider.this.getStreamTypes(uri, mimeTypeFilter);
+        }
+
+        @Override
+        public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeType, Bundle opts)
+                throws FileNotFoundException {
+            enforceReadPermission(uri);
+            return ContentProvider.this.openTypedAssetFile(uri, mimeType, opts);
+        }
+
         private void enforceReadPermission(Uri uri) {
             final int uid = Binder.getCallingUid();
             if (uid == mMyUid) {
@@ -752,6 +766,164 @@
     }
 
     /**
+     * Helper to compare two MIME types, where one may be a pattern.
+     * @param concreteType A fully-specified MIME type.
+     * @param desiredType A desired MIME type that may be a pattern such as *\/*.
+     * @return Returns true if the two MIME types match.
+     */
+    public static boolean compareMimeTypes(String concreteType, String desiredType) {
+        final int typeLength = desiredType.length();
+        if (typeLength == 3 && desiredType.equals("*/*")) {
+            return true;
+        }
+
+        final int slashpos = desiredType.indexOf('/');
+        if (slashpos > 0) {
+            if (typeLength == slashpos+2 && desiredType.charAt(slashpos+1) == '*') {
+                if (desiredType.regionMatches(0, concreteType, 0, slashpos+1)) {
+                    return true;
+                }
+            } else if (desiredType.equals(concreteType)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Called by a client to determine the types of data streams that this
+     * content provider supports for the given URI.  The default implementation
+     * returns null, meaning no types.  If your content provider stores data
+     * of a particular type, return that MIME type if it matches the given
+     * mimeTypeFilter.  If it can perform type conversions, return an array
+     * of all supported MIME types that match mimeTypeFilter.
+     *
+     * @param uri The data in the content provider being queried.
+     * @param mimeTypeFilter The type of data the client desires.  May be
+     * a pattern, such as *\/* to retrieve all possible data types.
+     * @returns Returns null if there are no possible data streams for the
+     * given mimeTypeFilter.  Otherwise returns an array of all available
+     * concrete MIME types.
+     *
+     * @see #getType(Uri)
+     * @see #openTypedAssetFile(Uri, String, Bundle)
+     * @see #compareMimeTypes(String, String)
+     */
+    public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
+        return null;
+    }
+
+    /**
+     * Called by a client to open a read-only stream containing data of a
+     * particular MIME type.  This is like {@link #openAssetFile(Uri, String)},
+     * except the file can only be read-only and the content provider may
+     * perform data conversions to generate data of the desired type.
+     *
+     * <p>The default implementation compares the given mimeType against the
+     * result of {@link #getType(Uri)} and, if the match, simple calls
+     * {@link #openAssetFile(Uri, String)}.
+     *
+     * <p>See {@link ClippedData} for examples of the use and implementation
+     * of this method.
+     *
+     * @param uri The data in the content provider being queried.
+     * @param mimeTypeFilter The type of data the client desires.  May be
+     * a pattern, such as *\/*, if the caller does not have specific type
+     * requirements; in this case the content provider will pick its best
+     * type matching the pattern.
+     * @param opts Additional options from the client.  The definitions of
+     * these are specific to the content provider being called.
+     *
+     * @return Returns a new AssetFileDescriptor from which the client can
+     * read data of the desired type.
+     *
+     * @throws FileNotFoundException Throws FileNotFoundException if there is
+     * no file associated with the given URI or the mode is invalid.
+     * @throws SecurityException Throws SecurityException if the caller does
+     * not have permission to access the data.
+     * @throws IllegalArgumentException Throws IllegalArgumentException if the
+     * content provider does not support the requested MIME type.
+     *
+     * @see #getStreamTypes(Uri, String)
+     * @see #openAssetFile(Uri, String)
+     * @see #compareMimeTypes(String, String)
+     */
+    public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
+            throws FileNotFoundException {
+        String baseType = getType(uri);
+        if (baseType != null && compareMimeTypes(baseType, mimeTypeFilter)) {
+            return openAssetFile(uri, "r");
+        }
+        throw new FileNotFoundException("Can't open " + uri + " as type " + mimeTypeFilter);
+    }
+
+    /**
+     * Interface to write a stream of data to a pipe.  Use with
+     * {@link ContentProvider#openPipeHelper}.
+     */
+    public interface PipeDataWriter<T> {
+        /**
+         * Called from a background thread to stream data out to a pipe.
+         * Note that the pipe is blocking, so this thread can block on
+         * writes for an arbitrary amount of time if the client is slow
+         * at reading.
+         *
+         * @param output The pipe where data should be written.  This will be
+         * closed for you upon returning from this function.
+         * @param uri The URI whose data is to be written.
+         * @param mimeType The desired type of data to be written.
+         * @param opts Options supplied by caller.
+         * @param args Your own custom arguments.
+         */
+        public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType,
+                Bundle opts, T args);
+    }
+
+    /**
+     * A helper function for implementing {@link #openTypedAssetFile}, for
+     * creating a data pipe and background thread allowing you to stream
+     * generated data back to the client.  This function returns a new
+     * ParcelFileDescriptor that should be returned to the caller (the caller
+     * is responsible for closing it).
+     *
+     * @param uri The URI whose data is to be written.
+     * @param mimeType The desired type of data to be written.
+     * @param opts Options supplied by caller.
+     * @param args Your own custom arguments.
+     * @param func Interface implementing the function that will actually
+     * stream the data.
+     * @return Returns a new ParcelFileDescriptor holding the read side of
+     * the pipe.  This should be returned to the caller for reading; the caller
+     * is responsible for closing it when done.
+     */
+    public <T> ParcelFileDescriptor openPipeHelper(final Uri uri, final String mimeType,
+            final Bundle opts, final T args, final PipeDataWriter<T> func)
+            throws FileNotFoundException {
+        try {
+            final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
+
+            AsyncTask<Object, Object, Object> task = new AsyncTask<Object, Object, Object>() {
+                @Override
+                protected Object doInBackground(Object... params) {
+                    func.writeDataToPipe(fds[1], uri, mimeType, opts, args);
+                    try {
+                        fds[1].close();
+                    } catch (IOException e) {
+                        Log.w(TAG, "Failure closing pipe", e);
+                    }
+                    return null;
+                }
+            };
+            task.execute((Object[])null);
+
+            return fds[0];
+        } catch (IOException e) {
+            throw new FileNotFoundException("failure making pipe");
+        }
+    }
+
+    /**
      * Returns true if this instance is a temporary content provider.
      * @return true if this instance is a temporary content provider
      */
@@ -777,6 +949,11 @@
      * @param info Registered information about this content provider
      */
     public void attachInfo(Context context, ProviderInfo info) {
+        /*
+         * We may be using AsyncTask from binder threads.  Make it init here
+         * so its static handler is on the main thread.
+         */
+        AsyncTask.init();
 
         /*
          * Only allow it to be set once, so after the content service gives
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 0858ea5..0540109 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -18,6 +18,7 @@
 
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ParcelFileDescriptor;
 import android.content.res.AssetFileDescriptor;
@@ -43,53 +44,77 @@
         mContentResolver = contentResolver;
     }
 
-    /** see {@link ContentProvider#query} */
+    /** See {@link ContentProvider#query ContentProvider.query} */
     public Cursor query(Uri url, String[] projection, String selection,
             String[] selectionArgs, String sortOrder) throws RemoteException {
         return mContentProvider.query(url, projection, selection,  selectionArgs, sortOrder);
     }
 
-    /** see {@link ContentProvider#getType} */
+    /** See {@link ContentProvider#getType ContentProvider.getType} */
     public String getType(Uri url) throws RemoteException {
         return mContentProvider.getType(url);
     }
 
-    /** see {@link ContentProvider#insert} */
+    /** See {@link ContentProvider#getStreamTypes ContentProvider.getStreamTypes} */
+    public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException {
+        return mContentProvider.getStreamTypes(url, mimeTypeFilter);
+    }
+
+    /** See {@link ContentProvider#insert ContentProvider.insert} */
     public Uri insert(Uri url, ContentValues initialValues)
             throws RemoteException {
         return mContentProvider.insert(url, initialValues);
     }
 
-    /** see {@link ContentProvider#bulkInsert} */
+    /** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */
     public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException {
         return mContentProvider.bulkInsert(url, initialValues);
     }
 
-    /** see {@link ContentProvider#delete} */
+    /** See {@link ContentProvider#delete ContentProvider.delete} */
     public int delete(Uri url, String selection, String[] selectionArgs)
             throws RemoteException {
         return mContentProvider.delete(url, selection, selectionArgs);
     }
 
-    /** see {@link ContentProvider#update} */
+    /** See {@link ContentProvider#update ContentProvider.update} */
     public int update(Uri url, ContentValues values, String selection,
             String[] selectionArgs) throws RemoteException {
         return mContentProvider.update(url, values, selection, selectionArgs);
     }
 
-    /** see {@link ContentProvider#openFile} */
+    /**
+     * See {@link ContentProvider#openFile ContentProvider.openFile}.  Note that
+     * this <em>does not</em>
+     * take care of non-content: URIs such as file:.  It is strongly recommended
+     * you use the {@link ContentResolver#openFileDescriptor
+     * ContentResolver.openFileDescriptor} API instead.
+     */
     public ParcelFileDescriptor openFile(Uri url, String mode)
             throws RemoteException, FileNotFoundException {
         return mContentProvider.openFile(url, mode);
     }
 
-    /** see {@link ContentProvider#openAssetFile} */
+    /**
+     * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
+     * Note that this <em>does not</em>
+     * take care of non-content: URIs such as file:.  It is strongly recommended
+     * you use the {@link ContentResolver#openAssetFileDescriptor
+     * ContentResolver.openAssetFileDescriptor} API instead.
+     */
     public AssetFileDescriptor openAssetFile(Uri url, String mode)
             throws RemoteException, FileNotFoundException {
         return mContentProvider.openAssetFile(url, mode);
     }
 
-     /** see {@link ContentProvider#applyBatch} */
+    /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
+    public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri,
+            String mimeType, Bundle opts)
+            throws RemoteException, FileNotFoundException {
+        return mContentProvider.openTypedAssetFile(uri, mimeType, opts);
+    }
+
+    /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */
     public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
             throws RemoteException, OperationApplicationException {
         return mContentProvider.applyBatch(operations);
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index fdb3d20..d1ab8d5 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -257,6 +257,38 @@
                     reply.writeBundle(responseBundle);
                     return true;
                 }
+
+                case GET_STREAM_TYPES_TRANSACTION:
+                {
+                    data.enforceInterface(IContentProvider.descriptor);
+                    Uri url = Uri.CREATOR.createFromParcel(data);
+                    String mimeTypeFilter = data.readString();
+                    String[] types = getStreamTypes(url, mimeTypeFilter);
+                    reply.writeNoException();
+                    reply.writeStringArray(types);
+
+                    return true;
+                }
+
+                case OPEN_TYPED_ASSET_FILE_TRANSACTION:
+                {
+                    data.enforceInterface(IContentProvider.descriptor);
+                    Uri url = Uri.CREATOR.createFromParcel(data);
+                    String mimeType = data.readString();
+                    Bundle opts = data.readBundle();
+
+                    AssetFileDescriptor fd;
+                    fd = openTypedAssetFile(url, mimeType, opts);
+                    reply.writeNoException();
+                    if (fd != null) {
+                        reply.writeInt(1);
+                        fd.writeToParcel(reply,
+                                Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+                    } else {
+                        reply.writeInt(0);
+                    }
+                    return true;
+                }
             }
         } catch (Exception e) {
             DatabaseUtils.writeExceptionToParcel(reply, e);
@@ -568,5 +600,50 @@
         return bundle;
     }
 
+    public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IContentProvider.descriptor);
+
+        url.writeToParcel(data, 0);
+        data.writeString(mimeTypeFilter);
+
+        mRemote.transact(IContentProvider.GET_STREAM_TYPES_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionFromParcel(reply);
+        String[] out = reply.createStringArray();
+
+        data.recycle();
+        reply.recycle();
+
+        return out;
+    }
+
+    public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
+            throws RemoteException, FileNotFoundException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IContentProvider.descriptor);
+
+        url.writeToParcel(data, 0);
+        data.writeString(mimeType);
+        data.writeBundle(opts);
+
+        mRemote.transact(IContentProvider.OPEN_TYPED_ASSET_FILE_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply);
+        int has = reply.readInt();
+        AssetFileDescriptor fd = has != 0
+                ? AssetFileDescriptor.CREATOR.createFromParcel(reply) : null;
+
+        data.recycle();
+        reply.recycle();
+
+        return fd;
+    }
+
     private IBinder mRemote;
 }
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 2ea0df96..22feb9a 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -186,8 +186,7 @@
      * using the content:// scheme.
      * @return A MIME type for the content, or null if the URL is invalid or the type is unknown
      */
-    public final String getType(Uri url)
-    {
+    public final String getType(Uri url) {
         IContentProvider provider = acquireProvider(url);
         if (provider == null) {
             return null;
@@ -204,6 +203,39 @@
     }
 
     /**
+     * Query for the possible MIME types for the representations the given
+     * content URL can be returned when opened as as stream with
+     * {@link #openTypedAssetFileDescriptor}.  Note that the types here are
+     * not necessarily a superset of the type returned by {@link #getType} --
+     * many content providers can not return a raw stream for the structured
+     * data that they contain.
+     *
+     * @param url A Uri identifying content (either a list or specific type),
+     * using the content:// scheme.
+     * @param mimeTypeFilter The desired MIME type.  This may be a pattern,
+     * such as *\/*, to query for all available MIME types that match the
+     * pattern.
+     * @return Returns an array of MIME type strings for all availablle
+     * data streams that match the given mimeTypeFilter.  If there are none,
+     * null is returned.
+     */
+    public String[] getStreamTypes(Uri url, String mimeTypeFilter) {
+        IContentProvider provider = acquireProvider(url);
+        if (provider == null) {
+            return null;
+        }
+        try {
+            return provider.getStreamTypes(url, mimeTypeFilter);
+        } catch (RemoteException e) {
+            return null;
+        } catch (java.lang.Exception e) {
+            return null;
+        } finally {
+            releaseProvider(provider);
+        }
+    }
+
+    /**
      * <p>
      * Query the given URI, returning a {@link Cursor} over the result set.
      * </p>
@@ -349,7 +381,7 @@
     }
 
     /**
-     * Open a raw file descriptor to access data under a "content:" URI.  This
+     * Open a raw file descriptor to access data under a URI.  This
      * is like {@link #openAssetFileDescriptor(Uri, String)}, but uses the
      * underlying {@link ContentProvider#openFile}
      * ContentProvider.openFile()} method, so will <em>not</em> work with
@@ -399,7 +431,7 @@
     }
 
     /**
-     * Open a raw file descriptor to access data under a "content:" URI.  This
+     * Open a raw file descriptor to access data under a URI.  This
      * interacts with the underlying {@link ContentProvider#openAssetFile}
      * method of the provider associated with the given URI, to retrieve any file stored there.
      *
@@ -433,6 +465,11 @@
      * </li>
      * </ul>
      *
+     * <p>Note that if this function is called for read-only input (mode is "r")
+     * on a content: URI, it will instead call {@link #openTypedAssetFileDescriptor}
+     * for you with a MIME type of "*\/*".  This allows such callers to benefit
+     * from any built-in data conversion that a provider implements.
+     *
      * @param uri The desired URI to open.
      * @param mode The file mode to use, as per {@link ContentProvider#openAssetFile
      * ContentProvider.openAssetFile}.
@@ -459,29 +496,97 @@
                     new File(uri.getPath()), modeToMode(uri, mode));
             return new AssetFileDescriptor(pfd, 0, -1);
         } else {
-            IContentProvider provider = acquireProvider(uri);
-            if (provider == null) {
-                throw new FileNotFoundException("No content provider: " + uri);
-            }
-            try {
-                AssetFileDescriptor fd = provider.openAssetFile(uri, mode);
-                if(fd == null) {
-                    releaseProvider(provider);
-                    return null;
+            if ("r".equals(mode)) {
+                return openTypedAssetFileDescriptor(uri, "*/*", null);
+            } else {
+                IContentProvider provider = acquireProvider(uri);
+                if (provider == null) {
+                    throw new FileNotFoundException("No content provider: " + uri);
                 }
-                ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
-                        fd.getParcelFileDescriptor(), provider);
-                return new AssetFileDescriptor(pfd, fd.getStartOffset(),
-                        fd.getDeclaredLength());
-            } catch (RemoteException e) {
+                try {
+                    AssetFileDescriptor fd = provider.openAssetFile(uri, mode);
+                    if(fd == null) {
+                        releaseProvider(provider);
+                        return null;
+                    }
+                    ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
+                            fd.getParcelFileDescriptor(), provider);
+
+                    // Success!  Don't release the provider when exiting, let
+                    // ParcelFileDescriptorInner do that when it is closed.
+                    provider = null;
+
+                    return new AssetFileDescriptor(pfd, fd.getStartOffset(),
+                            fd.getDeclaredLength());
+                } catch (RemoteException e) {
+                    throw new FileNotFoundException("Dead content provider: " + uri);
+                } catch (FileNotFoundException e) {
+                    throw e;
+                } finally {
+                    if (provider != null) {
+                        releaseProvider(provider);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Open a raw file descriptor to access (potentially type transformed)
+     * data from a "content:" URI.  This interacts with the underlying
+     * {@link ContentProvider#openTypedAssetFile} method of the provider
+     * associated with the given URI, to retrieve retrieve any appropriate
+     * data stream for the data stored there.
+     *
+     * <p>Unlike {@link #openAssetFileDescriptor}, this function only works
+     * with "content:" URIs, because content providers are the only facility
+     * with an associated MIME type to ensure that the returned data stream
+     * is of the desired type.
+     *
+     * <p>All text/* streams are encoded in UTF-8.
+     *
+     * @param uri The desired URI to open.
+     * @param mimeType The desired MIME type of the returned data.  This can
+     * be a pattern such as *\/*, which will allow the content provider to
+     * select a type, though there is no way for you to determine what type
+     * it is returning.
+     * @param opts Additional provider-dependent options.
+     * @return Returns a new ParcelFileDescriptor from which you can read the
+     * data stream from the provider.  Note that this may be a pipe, meaning
+     * you can't seek in it.  The only seek you should do is if the
+     * AssetFileDescriptor contains an offset, to move to that offset before
+     * reading.  You own this descriptor and are responsible for closing it when done.
+     * @throws FileNotFoundException Throws FileNotFoundException of no
+     * data of the desired type exists under the URI.
+     */
+    public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri,
+            String mimeType, Bundle opts) throws FileNotFoundException {
+        IContentProvider provider = acquireProvider(uri);
+        if (provider == null) {
+            throw new FileNotFoundException("No content provider: " + uri);
+        }
+        try {
+            AssetFileDescriptor fd = provider.openTypedAssetFile(uri, mimeType, opts);
+            if (fd == null) {
                 releaseProvider(provider);
-                throw new FileNotFoundException("Dead content provider: " + uri);
-            } catch (FileNotFoundException e) {
+                return null;
+            }
+            ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
+                    fd.getParcelFileDescriptor(), provider);
+
+            // Success!  Don't release the provider when exiting, let
+            // ParcelFileDescriptorInner do that when it is closed.
+            provider = null;
+
+            return new AssetFileDescriptor(pfd, fd.getStartOffset(),
+                    fd.getDeclaredLength());
+        } catch (RemoteException e) {
+            throw new FileNotFoundException("Dead content provider: " + uri);
+        } catch (FileNotFoundException e) {
+            throw e;
+        } finally {
+            if (provider != null) {
                 releaseProvider(provider);
-                throw e;
-            } catch (RuntimeException e) {
-                releaseProvider(provider);
-                throw e;
             }
         }
     }
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index 67e7581..8f122ce 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -59,6 +59,7 @@
             throws RemoteException, FileNotFoundException;
     public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
             throws RemoteException, OperationApplicationException;
+
     /**
      * @hide -- until interface has proven itself
      *
@@ -71,6 +72,11 @@
      */
     public Bundle call(String method, String request, Bundle args) throws RemoteException;
 
+    // Data interchange.
+    public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException;
+    public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
+            throws RemoteException, FileNotFoundException;
+
     /* IPC constants */
     static final String descriptor = "android.content.IContentProvider";
 
@@ -84,4 +90,6 @@
     static final int OPEN_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 14;
     static final int APPLY_BATCH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 19;
     static final int CALL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 20;
+    static final int GET_STREAM_TYPES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 21;
+    static final int OPEN_TYPED_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 22;
 }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 2acc4a0..58174fb 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -985,6 +985,15 @@
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_INSERT = "android.intent.action.INSERT";
     /**
+     * Activity Action: Create a new item in the given container, initializing it
+     * from the current contents of the clipboard.
+     * <p>Input: {@link #getData} is URI of the directory (vnd.android.cursor.dir/*)
+     * in which to place the data.
+     * <p>Output: URI of the new data that was created.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_PASTE = "android.intent.action.PASTE";
+    /**
      * Activity Action: Delete the given data from its container.
      * <p>Input: {@link #getData} is URI of data to be deleted.
      * <p>Output: nothing.
diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
index a37e4e8..ccb8605 100644
--- a/core/java/android/content/res/AssetFileDescriptor.java
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -129,8 +129,12 @@
     /**
      * Checks whether this file descriptor is for a memory file.
      */
-    private boolean isMemoryFile() throws IOException {
-        return MemoryFile.isMemoryFile(mFd.getFileDescriptor());
+    private boolean isMemoryFile() {
+        try {
+            return MemoryFile.isMemoryFile(mFd.getFileDescriptor());
+        } catch (IOException e) {
+            return false;
+        }
     }
 
     /**
diff --git a/core/java/android/net/DownloadManager.java b/core/java/android/net/DownloadManager.java
index 1d88c18..e69c324 100644
--- a/core/java/android/net/DownloadManager.java
+++ b/core/java/android/net/DownloadManager.java
@@ -241,12 +241,6 @@
      */
     public static class Request {
         /**
-         * Bit flag for {@link #setShowNotification} indicating a notification should be created
-         * while the download is running.
-         */
-        public static final int NOTIFICATION_WHEN_RUNNING = 1;
-
-        /**
          * Bit flag for {@link #setAllowedNetworkTypes} corresponding to
          * {@link ConnectivityManager#TYPE_MOBILE}.
          */
@@ -269,7 +263,7 @@
         private Map<String, String> mRequestHeaders = new HashMap<String, String>();
         private String mTitle;
         private String mDescription;
-        private int mNotificationFlags = 0;
+        private boolean mShowNotification = true;
         private String mMediaType;
         private boolean mRoamingAllowed = true;
         private int mAllowedNetworkTypes = ~0; // default to all network types allowed
@@ -344,15 +338,20 @@
         }
 
         /**
-         * Control system notifications posted by the download manager for this download.  If
-         * enabled, the download manager posts notifications about downloads through the system
-         * {@link android.app.NotificationManager}. By default, no notification is shown.
+         * Control whether a system notification is posted by the download manager while this
+         * download is running. If enabled, the download manager posts notifications about downloads
+         * through the system {@link android.app.NotificationManager}. By default, a notification is
+         * shown.
          *
-         * @param flags any combination of the NOTIFICATION_* bit flags
+         * If set to false, this requires the permission
+         * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION.
+         *
+         * @param show whether the download manager should show a notification for this download.
          * @return this object
+         * @hide
          */
-        public Request setShowNotification(int flags) {
-            mNotificationFlags = flags;
+        public Request setShowRunningNotification(boolean show) {
+            mShowNotification = show;
             return this;
         }
 
@@ -404,11 +403,9 @@
             putIfNonNull(values, Downloads.COLUMN_DESCRIPTION, mDescription);
             putIfNonNull(values, Downloads.COLUMN_MIME_TYPE, mMediaType);
 
-            int visibility = Downloads.VISIBILITY_HIDDEN;
-            if ((mNotificationFlags & NOTIFICATION_WHEN_RUNNING) != 0) {
-                visibility = Downloads.VISIBILITY_VISIBLE;
-            }
-            values.put(Downloads.COLUMN_VISIBILITY, visibility);
+            values.put(Downloads.COLUMN_VISIBILITY,
+                    mShowNotification ? Downloads.VISIBILITY_VISIBLE
+                            : Downloads.VISIBILITY_HIDDEN);
 
             values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes);
             values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed);
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
index 832ce84..aadacab 100644
--- a/core/java/android/os/AsyncTask.java
+++ b/core/java/android/os/AsyncTask.java
@@ -175,6 +175,11 @@
         FINISHED,
     }
 
+    /** @hide Used to force static handler to be created. */
+    public static void init() {
+        sHandler.getLooper();
+    }
+
     /**
      * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
      */
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index a17b7fe..72e21de 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -85,6 +85,8 @@
 
     public static native int getPermissions(String file, int[] outPermissions);
 
+    public static native int setUMask(int mask);
+
     /** returns the FAT file system volume ID for the volume mounted 
      * at the given mount point, or -1 for failure
      * @param mount point for FAT volume
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 9d213b3..d853f13 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -134,6 +134,25 @@
     private static native FileDescriptor getFileDescriptorFromSocket(Socket socket);
 
     /**
+     * Create two ParcelFileDescriptors structured as a data pipe.  The first
+     * ParcelFileDescriptor in the returned array is the read side; the second
+     * is the write side.
+     */
+    public static ParcelFileDescriptor[] createPipe() throws IOException {
+        FileDescriptor[] fds = new FileDescriptor[2];
+        int res = createPipeNative(fds);
+        if (res == 0) {
+            ParcelFileDescriptor[] pfds = new ParcelFileDescriptor[2];
+            pfds[0] = new ParcelFileDescriptor(fds[0]);
+            pfds[1] = new ParcelFileDescriptor(fds[1]);
+            return pfds;
+        }
+        throw new IOException("Unable to create pipe: errno=" + -res);
+    }
+
+    private static native int createPipeNative(FileDescriptor[] outFds);
+
+    /**
      * Retrieve the actual FileDescriptor associated with this object.
      * 
      * @return Returns the FileDescriptor associated with this object.
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index 1b37107..c9b5512 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -624,13 +624,19 @@
                 "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS";
 
         /**
-         * The permission to downloads files to the cache partition that won't be automatically
+         * The permission to download files to the cache partition that won't be automatically
          * purged when space is needed.
          */
         public static final String PERMISSION_CACHE_NON_PURGEABLE =
                 "android.permission.DOWNLOAD_CACHE_NON_PURGEABLE";
 
         /**
+         * The permission to download files without any system notification being shown.
+         */
+        public static final String PERMISSION_NO_NOTIFICATION =
+                "android.permission.DOWNLOAD_WITHOUT_NOTIFICATION";
+
+        /**
          * The content:// URI for the data table in the provider
          */
         public static final Uri CONTENT_URI =
diff --git a/core/java/android/util/JsonReader.java b/core/java/android/util/JsonReader.java
new file mode 100644
index 0000000..3345bfa
--- /dev/null
+++ b/core/java/android/util/JsonReader.java
@@ -0,0 +1,1058 @@
+/*
+ * 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.util;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Reads a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
+ * encoded value as a stream of tokens. This stream includes both literal
+ * values (strings, numbers, booleans, and nulls) as well as the begin and
+ * end delimiters of objects and arrays. The tokens are traversed in
+ * depth-first order, the same order that they appear in the JSON document.
+ * Within JSON objects, name/value pairs are represented by a single token.
+ *
+ * <h3>Parsing JSON</h3>
+ * To create a recursive descent parser your own JSON streams, first create an
+ * entry point method that creates a {@code JsonReader}.
+ *
+ * <p>Next, create handler methods for each structure in your JSON text. You'll
+ * need a method for each object type and for each array type.
+ * <ul>
+ *   <li>Within <strong>array handling</strong> methods, first call {@link
+ *       #beginArray} to consume the array's opening bracket. Then create a
+ *       while loop that accumulates values, terminating when {@link #hasNext}
+ *       is false. Finally, read the array's closing bracket by calling {@link
+ *       #endArray}.
+ *   <li>Within <strong>object handling</strong> methods, first call {@link
+ *       #beginObject} to consume the object's opening brace. Then create a
+ *       while loop that assigns values to local variables based on their name.
+ *       This loop should terminate when {@link #hasNext} is false. Finally,
+ *       read the object's closing brace by calling {@link #endObject}.
+ * </ul>
+ * <p>When a nested object or array is encountered, delegate to the
+ * corresponding handler method.
+ *
+ * <p>When an unknown name is encountered, strict parsers should fail with an
+ * exception. Lenient parsers should call {@link #skipValue()} to recursively
+ * skip the value's nested tokens, which may otherwise conflict.
+ *
+ * <p>If a value may be null, you should first check using {@link #peek()}.
+ * Null literals can be consumed using either {@link #nextNull()} or {@link
+ * #skipValue()}.
+ *
+ * <h3>Example</h3>
+ * Suppose we'd like to parse a stream of messages such as the following: <pre> {@code
+ * [
+ *   {
+ *     "id": 912345678901,
+ *     "text": "How do I read JSON on Android?",
+ *     "geo": null,
+ *     "user": {
+ *       "name": "android_newb",
+ *       "followers_count": 41
+ *      }
+ *   },
+ *   {
+ *     "id": 912345678902,
+ *     "text": "@android_newb just use android.util.JsonReader!",
+ *     "geo": [50.454722, -104.606667],
+ *     "user": {
+ *       "name": "jesse",
+ *       "followers_count": 2
+ *     }
+ *   }
+ * ]}</pre>
+ * This code implements the parser for the above structure: <pre>   {@code
+ *
+ *   public List<Message> readJsonStream(InputStream in) throws IOException {
+ *     JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
+ *     return readMessagesArray(reader);
+ *   }
+ *
+ *   public List<Message> readMessagesArray(JsonReader reader) throws IOException {
+ *     List<Message> messages = new ArrayList<Message>();
+ *
+ *     reader.beginArray();
+ *     while (reader.hasNext()) {
+ *       messages.add(readMessage(reader));
+ *     }
+ *     reader.endArray();
+ *     return messages;
+ *   }
+ *
+ *   public Message readMessage(JsonReader reader) throws IOException {
+ *     long id = -1;
+ *     String text = null;
+ *     User user = null;
+ *     List<Double> geo = null;
+ *
+ *     reader.beginObject();
+ *     while (reader.hasNext()) {
+ *       String name = reader.nextName();
+ *       if (name.equals("id")) {
+ *         id = reader.nextLong();
+ *       } else if (name.equals("text")) {
+ *         text = reader.nextString();
+ *       } else if (name.equals("geo") && reader.peek() != JsonToken.NULL) {
+ *         geo = readDoublesArray(reader);
+ *       } else if (name.equals("user")) {
+ *         user = readUser(reader);
+ *       } else {
+ *         reader.skipValue();
+ *       }
+ *     }
+ *     reader.endObject();
+ *     return new Message(id, text, user, geo);
+ *   }
+ *
+ *   public List<Double> readDoublesArray(JsonReader reader) throws IOException {
+ *     List<Double> doubles = new ArrayList<Double>();
+ *
+ *     reader.beginArray();
+ *     while (reader.hasNext()) {
+ *       doubles.add(reader.nextDouble());
+ *     }
+ *     reader.endArray();
+ *     return doubles;
+ *   }
+ *
+ *   public User readUser(JsonReader reader) throws IOException {
+ *     String username = null;
+ *     int followersCount = -1;
+ *
+ *     reader.beginObject();
+ *     while (reader.hasNext()) {
+ *       String name = reader.nextName();
+ *       if (name.equals("name")) {
+ *         username = reader.nextString();
+ *       } else if (name.equals("followers_count")) {
+ *         followersCount = reader.nextInt();
+ *       } else {
+ *         reader.skipValue();
+ *       }
+ *     }
+ *     reader.endObject();
+ *     return new User(username, followersCount);
+ *   }}</pre>
+ *
+ * <h3>Number Handling</h3>
+ * This reader permits numeric values to be read as strings and string values to
+ * be read as numbers. For example, both elements of the JSON array {@code
+ * [1, "1"]} may be read using either {@link #nextInt} or {@link #nextString}.
+ * This behavior is intended to prevent lossy numeric conversions: double is
+ * JavaScript's only numeric type and very large values like {@code
+ * 9007199254740993} cannot be represented exactly on that platform. To minimize
+ * precision loss, extremely large values should be written and read as strings
+ * in JSON.
+ *
+ * <p>Each {@code JsonReader} may be used to read a single JSON stream. Instances
+ * of this class are not thread safe.
+ */
+public final class JsonReader implements Closeable {
+
+    /** The input JSON. */
+    private final Reader in;
+
+    /** True to accept non-spec compliant JSON */
+    private boolean lenient = false;
+
+    /**
+     * Use a manual buffer to easily read and unread upcoming characters, and
+     * also so we can create strings without an intermediate StringBuilder.
+     */
+    private final char[] buffer = new char[1024];
+    private int pos = 0;
+    private int limit = 0;
+
+    private final List<JsonScope> stack = new ArrayList<JsonScope>();
+    {
+        push(JsonScope.EMPTY_DOCUMENT);
+    }
+
+    /**
+     * True if we've already read the next token. If we have, the string value
+     * for that token will be assigned to {@code value} if such a string value
+     * exists. And the token type will be assigned to {@code token} if the token
+     * type is known. The token type may be null for literals, since we derive
+     * that lazily.
+     */
+    private boolean hasToken;
+
+    /**
+     * The type of the next token to be returned by {@link #peek} and {@link
+     * #advance}, or {@code null} if it is unknown and must first be derived
+     * from {@code value}. This value is undefined if {@code hasToken} is false.
+     */
+    private JsonToken token;
+
+    /** The text of the next name. */
+    private String name;
+
+    /** The text of the next literal value. */
+    private String value;
+
+    /** True if we're currently handling a skipValue() call. */
+    private boolean skipping = false;
+
+    /**
+     * Creates a new instance that reads a JSON-encoded stream from {@code in}.
+     */
+    public JsonReader(Reader in) {
+        if (in == null) {
+            throw new NullPointerException("in == null");
+        }
+        this.in = in;
+    }
+
+    /**
+     * Configure this parser to be  be liberal in what it accepts. By default,
+     * this parser is strict and only accepts JSON as specified by <a
+     * href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the
+     * parser to lenient causes it to ignore the following syntax errors:
+     *
+     * <ul>
+     *   <li>End of line comments starting with {@code //} or {@code #} and
+     *       ending with a newline character.
+     *   <li>C-style comments starting with {@code /*} and ending with
+     *       {@code *}{@code /}. Such comments may not be nested.
+     *   <li>Names that are unquoted or {@code 'single quoted'}.
+     *   <li>Strings that are unquoted or {@code 'single quoted'}.
+     *   <li>Array elements separated by {@code ;} instead of {@code ,}.
+     *   <li>Unnecessary array separators. These are interpreted as if null
+     *       was the omitted value.
+     *   <li>Names and values separated by {@code =} or {@code =>} instead of
+     *       {@code :}.
+     *   <li>Name/value pairs separated by {@code ;} instead of {@code ,}.
+     * </ul>
+     */
+    public void setLenient(boolean lenient) {
+        this.lenient = lenient;
+    }
+
+    /**
+     * Consumes the next token from the JSON stream and asserts that it is the
+     * beginning of a new array.
+     */
+    public void beginArray() throws IOException {
+        expect(JsonToken.BEGIN_ARRAY);
+    }
+
+    /**
+     * Consumes the next token from the JSON stream and asserts that it is the
+     * end of the current array.
+     */
+    public void endArray() throws IOException {
+        expect(JsonToken.END_ARRAY);
+    }
+
+    /**
+     * Consumes the next token from the JSON stream and asserts that it is the
+     * beginning of a new object.
+     */
+    public void beginObject() throws IOException {
+        expect(JsonToken.BEGIN_OBJECT);
+    }
+
+    /**
+     * Consumes the next token from the JSON stream and asserts that it is the
+     * end of the current array.
+     */
+    public void endObject() throws IOException {
+        expect(JsonToken.END_OBJECT);
+    }
+
+    /**
+     * Consumes {@code expected}.
+     */
+    private void expect(JsonToken expected) throws IOException {
+        quickPeek();
+        if (token != expected) {
+            throw new IllegalStateException("Expected " + expected + " but was " + peek());
+        }
+        advance();
+    }
+
+    /**
+     * Returns true if the current array or object has another element.
+     */
+    public boolean hasNext() throws IOException {
+        quickPeek();
+        return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY;
+    }
+
+    /**
+     * Returns the type of the next token without consuming it.
+     */
+    public JsonToken peek() throws IOException {
+        quickPeek();
+
+        if (token == null) {
+            decodeLiteral();
+        }
+
+        return token;
+    }
+
+    /**
+     * Ensures that a token is ready. After this call either {@code token} or
+     * {@code value} will be non-null. To ensure {@code token} has a definitive
+     * value, use {@link #peek()}
+     */
+    private JsonToken quickPeek() throws IOException {
+        if (hasToken) {
+            return token;
+        }
+
+        switch (peekStack()) {
+            case EMPTY_DOCUMENT:
+                replaceTop(JsonScope.NONEMPTY_DOCUMENT);
+                JsonToken firstToken = nextValue();
+                if (token != JsonToken.BEGIN_ARRAY && token != JsonToken.BEGIN_OBJECT) {
+                    throw new IOException(
+                            "Expected JSON document to start with '[' or '{' but was " + token);
+                }
+                return firstToken;
+            case EMPTY_ARRAY:
+                return nextInArray(true);
+            case NONEMPTY_ARRAY:
+                return nextInArray(false);
+            case EMPTY_OBJECT:
+                return nextInObject(true);
+            case DANGLING_NAME:
+                return objectValue();
+            case NONEMPTY_OBJECT:
+                return nextInObject(false);
+            case NONEMPTY_DOCUMENT:
+                hasToken = true;
+                return token = JsonToken.END_DOCUMENT;
+            case CLOSED:
+                throw new IllegalStateException("JsonReader is closed");
+            default:
+                throw new AssertionError();
+        }
+    }
+
+    /**
+     * Advances the cursor in the JSON stream to the next token.
+     */
+    private JsonToken advance() throws IOException {
+        quickPeek();
+
+        JsonToken result = token;
+        hasToken = false;
+        token = null;
+        value = null;
+        name = null;
+        return result;
+    }
+
+    /**
+     * Returns the next token, a {@link JsonToken#NAME property name}, and
+     * consumes it.
+     *
+     * @throws IOException if the next token in the stream is not a property
+     *     name.
+     */
+    public String nextName() throws IOException {
+        quickPeek();
+        if (token != JsonToken.NAME) {
+            throw new IllegalStateException("Expected a name but was " + peek());
+        }
+        String result = name;
+        advance();
+        return result;
+    }
+
+    /**
+     * Returns the {@link JsonToken#STRING string} value of the next token,
+     * consuming it. If the next token is a number, this method will return its
+     * string form.
+     *
+     * @throws IllegalStateException if the next token is not a string or if
+     *     this reader is closed.
+     */
+    public String nextString() throws IOException {
+        peek();
+        if (value == null || (token != JsonToken.STRING && token != JsonToken.NUMBER)) {
+            throw new IllegalStateException("Expected a string but was " + peek());
+        }
+
+        String result = value;
+        advance();
+        return result;
+    }
+
+    /**
+     * Returns the {@link JsonToken#BOOLEAN boolean} value of the next token,
+     * consuming it.
+     *
+     * @throws IllegalStateException if the next token is not a boolean or if
+     *     this reader is closed.
+     */
+    public boolean nextBoolean() throws IOException {
+        quickPeek();
+        if (value == null || token == JsonToken.STRING) {
+            throw new IllegalStateException("Expected a boolean but was " + peek());
+        }
+
+        boolean result;
+        if (value.equalsIgnoreCase("true")) {
+            result = true;
+        } else if (value.equalsIgnoreCase("false")) {
+            result = false;
+        } else {
+            throw new IllegalStateException("Not a boolean: " + value);
+        }
+
+        advance();
+        return result;
+    }
+
+    /**
+     * Consumes the next token from the JSON stream and asserts that it is a
+     * literal null.
+     *
+     * @throws IllegalStateException if the next token is not null or if this
+     *     reader is closed.
+     */
+    public void nextNull() throws IOException {
+        quickPeek();
+        if (value == null || token == JsonToken.STRING) {
+            throw new IllegalStateException("Expected null but was " + peek());
+        }
+
+        if (!value.equalsIgnoreCase("null")) {
+            throw new IllegalStateException("Not a null: " + value);
+        }
+
+        advance();
+    }
+
+    /**
+     * Returns the {@link JsonToken#NUMBER double} value of the next token,
+     * consuming it. If the next token is a string, this method will attempt to
+     * parse it as a double.
+     *
+     * @throws IllegalStateException if the next token is not a literal value.
+     * @throws NumberFormatException if the next literal value cannot be parsed
+     *     as a double, or is non-finite.
+     */
+    public double nextDouble() throws IOException {
+        quickPeek();
+        if (value == null) {
+            throw new IllegalStateException("Expected a double but was " + peek());
+        }
+
+        double result = Double.parseDouble(value);
+
+        if ((result >= 1.0d && value.startsWith("0"))
+                || Double.isNaN(result)
+                || Double.isInfinite(result)) {
+            throw new NumberFormatException(
+                    "JSON forbids octal prefixes, NaN and infinities: " + value);
+        }
+
+        advance();
+        return result;
+    }
+
+    /**
+     * Returns the {@link JsonToken#NUMBER long} value of the next token,
+     * consuming it. If the next token is a string, this method will attempt to
+     * parse it as a long. If the next token's numeric value cannot be exactly
+     * represented by a Java {@code long}, this method throws.
+     *
+     * @throws IllegalStateException if the next token is not a literal value.
+     * @throws NumberFormatException if the next literal value cannot be parsed
+     *     as a number, or exactly represented as a long.
+     */
+    public long nextLong() throws IOException {
+        quickPeek();
+        if (value == null) {
+            throw new IllegalStateException("Expected a long but was " + peek());
+        }
+
+        long result;
+        try {
+            result = Long.parseLong(value);
+        } catch (NumberFormatException ignored) {
+            double asDouble = Double.parseDouble(value); // don't catch this NumberFormatException
+            result = (long) asDouble;
+            if ((double) result != asDouble) {
+                throw new NumberFormatException(value);
+            }
+        }
+
+        if (result >= 1L && value.startsWith("0")) {
+            throw new NumberFormatException("JSON forbids octal prefixes: " + value);
+        }
+
+        advance();
+        return result;
+    }
+
+    /**
+     * Returns the {@link JsonToken#NUMBER int} value of the next token,
+     * consuming it. If the next token is a string, this method will attempt to
+     * parse it as an int. If the next token's numeric value cannot be exactly
+     * represented by a Java {@code int}, this method throws.
+     *
+     * @throws IllegalStateException if the next token is not a literal value.
+     * @throws NumberFormatException if the next literal value cannot be parsed
+     *     as a number, or exactly represented as an int.
+     */
+    public int nextInt() throws IOException {
+        quickPeek();
+        if (value == null) {
+            throw new IllegalStateException("Expected an int but was " + peek());
+        }
+
+        int result;
+        try {
+            result = Integer.parseInt(value);
+        } catch (NumberFormatException ignored) {
+            double asDouble = Double.parseDouble(value); // don't catch this NumberFormatException
+            result = (int) asDouble;
+            if ((double) result != asDouble) {
+                throw new NumberFormatException(value);
+            }
+        }
+
+        if (result >= 1L && value.startsWith("0")) {
+            throw new NumberFormatException("JSON forbids octal prefixes: " + value);
+        }
+
+        advance();
+        return result;
+    }
+
+    /**
+     * Closes this JSON reader and the underlying {@link Reader}.
+     */
+    public void close() throws IOException {
+        hasToken = false;
+        value = null;
+        token = null;
+        stack.clear();
+        stack.add(JsonScope.CLOSED);
+        in.close();
+    }
+
+    /**
+     * Skips the next value recursively. If it is an object or array, all nested
+     * elements are skipped. This method is intended for use when the JSON token
+     * stream contains unrecognized or unhandled values.
+     */
+    public void skipValue() throws IOException {
+        skipping = true;
+        try {
+            int count = 0;
+            do {
+                JsonToken token = advance();
+                if (token == JsonToken.BEGIN_ARRAY || token == JsonToken.BEGIN_OBJECT) {
+                    count++;
+                } else if (token == JsonToken.END_ARRAY || token == JsonToken.END_OBJECT) {
+                    count--;
+                }
+            } while (count != 0);
+        } finally {
+            skipping = false;
+        }
+    }
+
+    private JsonScope peekStack() {
+        return stack.get(stack.size() - 1);
+    }
+
+    private JsonScope pop() {
+        return stack.remove(stack.size() - 1);
+    }
+
+    private void push(JsonScope newTop) {
+        stack.add(newTop);
+    }
+
+    /**
+     * Replace the value on the top of the stack with the given value.
+     */
+    private void replaceTop(JsonScope newTop) {
+        stack.set(stack.size() - 1, newTop);
+    }
+
+    private JsonToken nextInArray(boolean firstElement) throws IOException {
+        if (firstElement) {
+            replaceTop(JsonScope.NONEMPTY_ARRAY);
+        } else {
+            /* Look for a comma before each element after the first element. */
+            switch (nextNonWhitespace()) {
+                case ']':
+                    pop();
+                    hasToken = true;
+                    return token = JsonToken.END_ARRAY;
+                case ';':
+                    checkLenient(); // fall-through
+                case ',':
+                    break;
+                default:
+                    throw syntaxError("Unterminated array");
+            }
+        }
+
+        switch (nextNonWhitespace()) {
+            case ']':
+                if (firstElement) {
+                    pop();
+                    hasToken = true;
+                    return token = JsonToken.END_ARRAY;
+                }
+                // fall-through to handle ",]"
+            case ';':
+            case ',':
+                /* In lenient mode, a 0-length literal means 'null' */
+                checkLenient();
+                pos--;
+                hasToken = true;
+                value = "null";
+                return token = JsonToken.NULL;
+            default:
+                pos--;
+                return nextValue();
+        }
+    }
+
+    private JsonToken nextInObject(boolean firstElement) throws IOException {
+        /*
+         * Read delimiters. Either a comma/semicolon separating this and the
+         * previous name-value pair, or a close brace to denote the end of the
+         * object.
+         */
+        if (firstElement) {
+            /* Peek to see if this is the empty object. */
+            switch (nextNonWhitespace()) {
+                case '}':
+                    pop();
+                    hasToken = true;
+                    return token = JsonToken.END_OBJECT;
+                default:
+                    pos--;
+            }
+        } else {
+            switch (nextNonWhitespace()) {
+                case '}':
+                    pop();
+                    hasToken = true;
+                    return token = JsonToken.END_OBJECT;
+                case ';':
+                case ',':
+                    break;
+                default:
+                    throw syntaxError("Unterminated object");
+            }
+        }
+
+        /* Read the name. */
+        int quote = nextNonWhitespace();
+        switch (quote) {
+            case '\'':
+                checkLenient(); // fall-through
+            case '"':
+                name = nextString((char) quote);
+                break;
+            default:
+                checkLenient();
+                pos--;
+                name = nextLiteral();
+                if (name.isEmpty()) {
+                    throw syntaxError("Expected name");
+                }
+        }
+
+        replaceTop(JsonScope.DANGLING_NAME);
+        hasToken = true;
+        return token = JsonToken.NAME;
+    }
+
+    private JsonToken objectValue() throws IOException {
+        /*
+         * Read the name/value separator. Usually a colon ':'. In lenient mode
+         * we also accept an equals sign '=', or an arrow "=>".
+         */
+        switch (nextNonWhitespace()) {
+            case ':':
+                break;
+            case '=':
+                checkLenient();
+                if ((pos < limit || fillBuffer(1)) && buffer[pos] == '>') {
+                    pos++;
+                }
+                break;
+            default:
+                throw syntaxError("Expected ':'");
+        }
+
+        replaceTop(JsonScope.NONEMPTY_OBJECT);
+        return nextValue();
+    }
+
+    private JsonToken nextValue() throws IOException {
+        int c = nextNonWhitespace();
+        switch (c) {
+            case '{':
+                push(JsonScope.EMPTY_OBJECT);
+                hasToken = true;
+                return token = JsonToken.BEGIN_OBJECT;
+
+            case '[':
+                push(JsonScope.EMPTY_ARRAY);
+                hasToken = true;
+                return token = JsonToken.BEGIN_ARRAY;
+
+            case '\'':
+                checkLenient(); // fall-through
+            case '"':
+                value = nextString((char) c);
+                hasToken = true;
+                return token = JsonToken.STRING;
+
+            default:
+                pos--;
+                return readLiteral();
+        }
+    }
+
+    /**
+     * Returns true once {@code limit - pos >= minimum}. If the data is
+     * exhausted before that many characters are available, this returns
+     * false.
+     */
+    private boolean fillBuffer(int minimum) throws IOException {
+        if (limit != pos) {
+            limit -= pos;
+            System.arraycopy(buffer, pos, buffer, 0, limit);
+        } else {
+            limit = 0;
+        }
+
+        pos = 0;
+        int total;
+        while ((total = in.read(buffer, limit, buffer.length - limit)) != -1) {
+            limit += total;
+            if (limit >= minimum) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private int nextNonWhitespace() throws IOException {
+        while (pos < limit || fillBuffer(1)) {
+            int c = buffer[pos++];
+            switch (c) {
+                case '\t':
+                case ' ':
+                case '\n':
+                case '\r':
+                    continue;
+
+                case '/':
+                    if (pos == limit && !fillBuffer(1)) {
+                        return c;
+                    }
+
+                    checkLenient();
+                    char peek = buffer[pos];
+                    switch (peek) {
+                        case '*':
+                            // skip a /* c-style comment */
+                            pos++;
+                            if (!skipTo("*/")) {
+                                throw syntaxError("Unterminated comment");
+                            }
+                            pos += 2;
+                            continue;
+
+                        case '/':
+                            // skip a // end-of-line comment
+                            pos++;
+                            skipToEndOfLine();
+                            continue;
+
+                        default:
+                            return c;
+                    }
+
+                case '#':
+                    /*
+                     * Skip a # hash end-of-line comment. The JSON RFC doesn't
+                     * specify this behaviour, but it's required to parse
+                     * existing documents. See http://b/2571423.
+                     */
+                    checkLenient();
+                    skipToEndOfLine();
+                    continue;
+
+                default:
+                    return c;
+            }
+        }
+
+        throw syntaxError("End of input");
+    }
+
+    private void checkLenient() throws IOException {
+        if (!lenient) {
+            throw syntaxError("Use JsonReader.setLenient(true) to accept malformed JSON");
+        }
+    }
+
+    /**
+     * Advances the position until after the next newline character. If the line
+     * is terminated by "\r\n", the '\n' must be consumed as whitespace by the
+     * caller.
+     */
+    private void skipToEndOfLine() throws IOException {
+        while (pos < limit || fillBuffer(1)) {
+            char c = buffer[pos++];
+            if (c == '\r' || c == '\n') {
+                break;
+            }
+        }
+    }
+
+    private boolean skipTo(String toFind) throws IOException {
+        outer:
+        for (; pos + toFind.length() < limit || fillBuffer(toFind.length()); pos++) {
+            for (int c = 0; c < toFind.length(); c++) {
+                if (buffer[pos + c] != toFind.charAt(c)) {
+                    continue outer;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the string up to but not including {@code quote}, unescaping any
+     * character escape sequences encountered along the way. The opening quote
+     * should have already been read. This consumes the closing quote, but does
+     * not include it in the returned string.
+     *
+     * @param quote either ' or ".
+     * @throws NumberFormatException if any unicode escape sequences are
+     *     malformed.
+     */
+    private String nextString(char quote) throws IOException {
+        StringBuilder builder = null;
+        do {
+            /* the index of the first character not yet appended to the builder. */
+            int start = pos;
+            while (pos < limit) {
+                int c = buffer[pos++];
+
+                if (c == quote) {
+                    if (skipping) {
+                        return "skipped!";
+                    } else if (builder == null) {
+                        return new String(buffer, start, pos - start - 1);
+                    } else {
+                        builder.append(buffer, start, pos - start - 1);
+                        return builder.toString();
+                    }
+
+                } else if (c == '\\') {
+                    if (builder == null) {
+                        builder = new StringBuilder();
+                    }
+                    builder.append(buffer, start, pos - start - 1);
+                    builder.append(readEscapeCharacter());
+                    start = pos;
+                }
+            }
+
+            if (builder == null) {
+                builder = new StringBuilder();
+            }
+            builder.append(buffer, start, pos - start);
+        } while (fillBuffer(1));
+
+        throw syntaxError("Unterminated string");
+    }
+
+    /**
+     * Returns the string up to but not including any delimiter characters. This
+     * does not consume the delimiter character.
+     */
+    private String nextLiteral() throws IOException {
+        StringBuilder builder = null;
+        do {
+            /* the index of the first character not yet appended to the builder. */
+            int start = pos;
+            while (pos < limit) {
+                int c = buffer[pos++];
+                switch (c) {
+                    case '/':
+                    case '\\':
+                    case ';':
+                    case '#':
+                    case '=':
+                        checkLenient(); // fall-through
+
+                    case '{':
+                    case '}':
+                    case '[':
+                    case ']':
+                    case ':':
+                    case ',':
+                    case ' ':
+                    case '\t':
+                    case '\f':
+                    case '\r':
+                    case '\n':
+                        pos--;
+                        if (skipping) {
+                            return "skipped!";
+                        } else if (builder == null) {
+                            return new String(buffer, start, pos - start);
+                        } else {
+                            builder.append(buffer, start, pos - start);
+                            return builder.toString();
+                        }
+                }
+            }
+
+            if (builder == null) {
+                builder = new StringBuilder();
+            }
+            builder.append(buffer, start, pos - start);
+        } while (fillBuffer(1));
+
+        return builder.toString();
+    }
+
+    @Override public String toString() {
+        return getClass().getSimpleName() + " near " + getSnippet();
+    }
+
+    /**
+     * Unescapes the character identified by the character or characters that
+     * immediately follow a backslash. The backslash '\' should have already
+     * been read. This supports both unicode escapes "u000A" and two-character
+     * escapes "\n".
+     *
+     * @throws NumberFormatException if any unicode escape sequences are
+     *     malformed.
+     */
+    private char readEscapeCharacter() throws IOException {
+        if (pos == limit && !fillBuffer(1)) {
+            throw syntaxError("Unterminated escape sequence");
+        }
+
+        char escaped = buffer[pos++];
+        switch (escaped) {
+            case 'u':
+                if (pos + 4 > limit && !fillBuffer(4)) {
+                    throw syntaxError("Unterminated escape sequence");
+                }
+                String hex = new String(buffer, pos, 4);
+                pos += 4;
+                return (char) Integer.parseInt(hex, 16);
+
+            case 't':
+                return '\t';
+
+            case 'b':
+                return '\b';
+
+            case 'n':
+                return '\n';
+
+            case 'r':
+                return '\r';
+
+            case 'f':
+                return '\f';
+
+            case '\'':
+            case '"':
+            case '\\':
+            default:
+                return escaped;
+        }
+    }
+
+    /**
+     * Reads a null, boolean, numeric or unquoted string literal value.
+     */
+    private JsonToken readLiteral() throws IOException {
+        String literal = nextLiteral();
+        if (literal.isEmpty()) {
+            throw syntaxError("Expected literal value");
+        }
+        value = literal;
+        hasToken = true;
+        return token = null; // use decodeLiteral() to get the token type
+    }
+
+    /**
+     * Assigns {@code nextToken} based on the value of {@code nextValue}.
+     */
+    private void decodeLiteral() throws IOException {
+        if (value.equalsIgnoreCase("null")) {
+            token = JsonToken.NULL;
+        } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
+            token = JsonToken.BOOLEAN;
+        } else {
+            try {
+                Double.parseDouble(value); // this work could potentially be cached
+                token = JsonToken.NUMBER;
+            } catch (NumberFormatException ignored) {
+                // this must be an unquoted string
+                checkLenient();
+                token = JsonToken.STRING;
+            }
+        }
+    }
+
+    /**
+     * Throws a new IO exception with the given message and a context snippet
+     * with this reader's content.
+     */
+    public IOException syntaxError(String message) throws IOException {
+        throw new JsonSyntaxException(message + " near " + getSnippet());
+    }
+
+    private CharSequence getSnippet() {
+        StringBuilder snippet = new StringBuilder();
+        int beforePos = Math.min(pos, 20);
+        snippet.append(buffer, pos - beforePos, beforePos);
+        int afterPos = Math.min(limit - pos, 20);
+        snippet.append(buffer, pos, afterPos);
+        return snippet;
+    }
+
+    private static class JsonSyntaxException extends IOException {
+        private JsonSyntaxException(String s) {
+            super(s);
+        }
+    }
+}
diff --git a/core/java/android/util/JsonScope.java b/core/java/android/util/JsonScope.java
new file mode 100644
index 0000000..ca534e9
--- /dev/null
+++ b/core/java/android/util/JsonScope.java
@@ -0,0 +1,68 @@
+/*
+ * 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.util;
+
+/**
+ * Lexical scoping elements within a JSON reader or writer.
+ */
+enum JsonScope {
+
+    /**
+     * An array with no elements requires no separators or newlines before
+     * it is closed.
+     */
+    EMPTY_ARRAY,
+
+    /**
+     * A array with at least one value requires a comma and newline before
+     * the next element.
+     */
+    NONEMPTY_ARRAY,
+
+    /**
+     * An object with no name/value pairs requires no separators or newlines
+     * before it is closed.
+     */
+    EMPTY_OBJECT,
+
+    /**
+     * An object whose most recent element is a key. The next element must
+     * be a value.
+     */
+    DANGLING_NAME,
+
+    /**
+     * An object with at least one name/value pair requires a comma and
+     * newline before the next element.
+     */
+    NONEMPTY_OBJECT,
+
+    /**
+     * No object or array has been started.
+     */
+    EMPTY_DOCUMENT,
+
+    /**
+     * A document with at an array or object.
+     */
+    NONEMPTY_DOCUMENT,
+
+    /**
+     * A document that's been closed and cannot be accessed.
+     */
+    CLOSED,
+}
diff --git a/core/java/android/util/JsonToken.java b/core/java/android/util/JsonToken.java
new file mode 100644
index 0000000..45bc6ca
--- /dev/null
+++ b/core/java/android/util/JsonToken.java
@@ -0,0 +1,82 @@
+/*
+ * 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.util;
+
+/**
+ * A structure, name or value type in a JSON-encoded string.
+ */
+public enum JsonToken {
+
+    /**
+     * The opening of a JSON array. Written using {@link JsonWriter#beginObject}
+     * and read using {@link JsonReader#beginObject}.
+     */
+    BEGIN_ARRAY,
+
+    /**
+     * The closing of a JSON array. Written using {@link JsonWriter#endArray}
+     * and read using {@link JsonReader#endArray}.
+     */
+    END_ARRAY,
+
+    /**
+     * The opening of a JSON object. Written using {@link JsonWriter#beginObject}
+     * and read using {@link JsonReader#beginObject}.
+     */
+    BEGIN_OBJECT,
+
+    /**
+     * The closing of a JSON object. Written using {@link JsonWriter#endObject}
+     * and read using {@link JsonReader#endObject}.
+     */
+    END_OBJECT,
+
+    /**
+     * A JSON property name. Within objects, tokens alternate between names and
+     * their values. Written using {@link JsonWriter#name} and read using {@link
+     * JsonReader#nextName}
+     */
+    NAME,
+
+    /**
+     * A JSON string.
+     */
+    STRING,
+
+    /**
+     * A JSON number represented in this API by a Java {@code double}, {@code
+     * long}, or {@code int}.
+     */
+    NUMBER,
+
+    /**
+     * A JSON {@code true} or {@code false}.
+     */
+    BOOLEAN,
+
+    /**
+     * A JSON {@code null}.
+     */
+    NULL,
+
+    /**
+     * The end of the JSON stream. This sentinel value is returned by {@link
+     * JsonReader#peek()} to signal that the JSON-encoded value has no more
+     * tokens.
+     */
+    END_DOCUMENT
+}
diff --git a/core/java/android/util/JsonWriter.java b/core/java/android/util/JsonWriter.java
new file mode 100644
index 0000000..fecc1c8
--- /dev/null
+++ b/core/java/android/util/JsonWriter.java
@@ -0,0 +1,472 @@
+/*
+ * 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.util;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Writes a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
+ * encoded value to a stream, one token at a time. The stream includes both
+ * literal values (strings, numbers, booleans and nulls) as well as the begin
+ * and end delimiters of objects and arrays.
+ *
+ * <h3>Encoding JSON</h3>
+ * To encode your data as JSON, create a new {@code JsonWriter}. Each JSON
+ * document must contain one top-level array or object. Call methods on the
+ * writer as you walk the structure's contents, nesting arrays and objects as
+ * necessary:
+ * <ul>
+ *   <li>To write <strong>arrays</strong>, first call {@link #beginArray()}.
+ *       Write each of the array's elements with the appropriate {@link #value}
+ *       methods or by nesting other arrays and objects. Finally close the array
+ *       using {@link #endArray()}.
+ *   <li>To write <strong>objects</strong>, first call {@link #beginObject()}.
+ *       Write each of the object's properties by alternating calls to
+ *       {@link #name} with the property's value. Write property values with the
+ *       appropriate {@link #value} method or by nesting other objects or arrays.
+ *       Finally close the object using {@link #endObject()}.
+ * </ul>
+ *
+ * <h3>Example</h3>
+ * Suppose we'd like to encode a stream of messages such as the following: <pre> {@code
+ * [
+ *   {
+ *     "id": 912345678901,
+ *     "text": "How do I write JSON on Android?",
+ *     "geo": null,
+ *     "user": {
+ *       "name": "android_newb",
+ *       "followers_count": 41
+ *      }
+ *   },
+ *   {
+ *     "id": 912345678902,
+ *     "text": "@android_newb just use android.util.JsonWriter!",
+ *     "geo": [50.454722, -104.606667],
+ *     "user": {
+ *       "name": "jesse",
+ *       "followers_count": 2
+ *     }
+ *   }
+ * ]}</pre>
+ * This code encodes the above structure: <pre>   {@code
+ *   public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException {
+ *     JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
+ *     writer.setIndentSpaces(4);
+ *     writeMessagesArray(writer, messages);
+ *     writer.close();
+ *   }
+ *
+ *   public void writeMessagesArray(JsonWriter writer, List<Message> messages) throws IOException {
+ *     writer.beginArray();
+ *     for (Message message : messages) {
+ *       writeMessage(writer, message);
+ *     }
+ *     writer.endArray();
+ *   }
+ *
+ *   public void writeMessage(JsonWriter writer, Message message) throws IOException {
+ *     writer.beginObject();
+ *     writer.name("id").value(message.getId());
+ *     writer.name("text").value(message.getText());
+ *     if (message.getGeo() != null) {
+ *       writer.name("geo");
+ *       writeDoublesArray(writer, message.getGeo());
+ *     } else {
+ *       writer.name("geo").nullValue();
+ *     }
+ *     writer.name("user");
+ *     writeUser(writer, message.getUser());
+ *     writer.endObject();
+ *   }
+ *
+ *   public void writeUser(JsonWriter writer, User user) throws IOException {
+ *     writer.beginObject();
+ *     writer.name("name").value(user.getName());
+ *     writer.name("followers_count").value(user.getFollowersCount());
+ *     writer.endObject();
+ *   }
+ *
+ *   public void writeDoublesArray(JsonWriter writer, List<Double> doubles) throws IOException {
+ *     writer.beginArray();
+ *     for (Double value : doubles) {
+ *       writer.value(value);
+ *     }
+ *     writer.endArray();
+ *   }}</pre>
+ *
+ * <p>Each {@code JsonWriter} may be used to write a single JSON stream.
+ * Instances of this class are not thread safe. Calls that would result in a
+ * malformed JSON string will fail with an {@link IllegalStateException}.
+ */
+public final class JsonWriter implements Closeable {
+
+    /** The output data, containing at most one top-level array or object. */
+    private final Writer out;
+
+    private final List<JsonScope> stack = new ArrayList<JsonScope>();
+    {
+        stack.add(JsonScope.EMPTY_DOCUMENT);
+    }
+
+    /**
+     * A string containing a full set of spaces for a single level of
+     * indentation, or null for no pretty printing.
+     */
+    private String indent;
+
+    /**
+     * The name/value separator; either ":" or ": ".
+     */
+    private String separator = ":";
+
+    /**
+     * Creates a new instance that writes a JSON-encoded stream to {@code out}.
+     * For best performance, ensure {@link Writer} is buffered; wrapping in
+     * {@link java.io.BufferedWriter BufferedWriter} if necessary.
+     */
+    public JsonWriter(Writer out) {
+        if (out == null) {
+            throw new NullPointerException("out == null");
+        }
+        this.out = out;
+    }
+
+    /**
+     * Sets the indentation string to be repeated for each level of indentation
+     * in the encoded document. If {@code indent.isEmpty()} the encoded document
+     * will be compact. Otherwise the encoded document will be more
+     * human-readable.
+     *
+     * @param indent a string containing only whitespace.
+     */
+    public void setIndent(String indent) {
+        if (indent.isEmpty()) {
+            this.indent = null;
+            this.separator = ":";
+        } else {
+            this.indent = indent;
+            this.separator = ": ";
+        }
+    }
+
+    /**
+     * Begins encoding a new array. Each call to this method must be paired with
+     * a call to {@link #endArray}.
+     *
+     * @return this writer.
+     */
+    public JsonWriter beginArray() throws IOException {
+        return open(JsonScope.EMPTY_ARRAY, "[");
+    }
+
+    /**
+     * Ends encoding the current array.
+     *
+     * @return this writer.
+     */
+    public JsonWriter endArray() throws IOException {
+        return close(JsonScope.EMPTY_ARRAY, JsonScope.NONEMPTY_ARRAY, "]");
+    }
+
+    /**
+     * Begins encoding a new object. Each call to this method must be paired
+     * with a call to {@link #endObject}.
+     *
+     * @return this writer.
+     */
+    public JsonWriter beginObject() throws IOException {
+        return open(JsonScope.EMPTY_OBJECT, "{");
+    }
+
+    /**
+     * Ends encoding the current object.
+     *
+     * @return this writer.
+     */
+    public JsonWriter endObject() throws IOException {
+        return close(JsonScope.EMPTY_OBJECT, JsonScope.NONEMPTY_OBJECT, "}");
+    }
+
+    /**
+     * Enters a new scope by appending any necessary whitespace and the given
+     * bracket.
+     */
+    private JsonWriter open(JsonScope empty, String openBracket) throws IOException {
+        beforeValue(true);
+        stack.add(empty);
+        out.write(openBracket);
+        return this;
+    }
+
+    /**
+     * Closes the current scope by appending any necessary whitespace and the
+     * given bracket.
+     */
+    private JsonWriter close(JsonScope empty, JsonScope nonempty, String closeBracket)
+            throws IOException {
+        JsonScope context = peek();
+        if (context != nonempty && context != empty) {
+            throw new IllegalStateException("Nesting problem: " + stack);
+        }
+
+        stack.remove(stack.size() - 1);
+        if (context == nonempty) {
+            newline();
+        }
+        out.write(closeBracket);
+        return this;
+    }
+
+    /**
+     * Returns the value on the top of the stack.
+     */
+    private JsonScope peek() {
+        return stack.get(stack.size() - 1);
+    }
+
+    /**
+     * Replace the value on the top of the stack with the given value.
+     */
+    private void replaceTop(JsonScope topOfStack) {
+        stack.set(stack.size() - 1, topOfStack);
+    }
+
+    /**
+     * Encodes the property name.
+     *
+     * @param name the name of the forthcoming value. May not be null.
+     * @return this writer.
+     */
+    public JsonWriter name(String name) throws IOException {
+        if (name == null) {
+            throw new NullPointerException("name == null");
+        }
+        beforeName();
+        string(name);
+        return this;
+    }
+
+    /**
+     * Encodes {@code value}.
+     *
+     * @param value the literal string value, or null to encode a null literal.
+     * @return this writer.
+     */
+    public JsonWriter value(String value) throws IOException {
+        if (value == null) {
+            return nullValue();
+        }
+        beforeValue(false);
+        string(value);
+        return this;
+    }
+
+    /**
+     * Encodes {@code null}.
+     *
+     * @return this writer.
+     */
+    public JsonWriter nullValue() throws IOException {
+        beforeValue(false);
+        out.write("null");
+        return this;
+    }
+
+    /**
+     * Encodes {@code value}.
+     *
+     * @return this writer.
+     */
+    public JsonWriter value(boolean value) throws IOException {
+        beforeValue(false);
+        out.write(value ? "true" : "false");
+        return this;
+    }
+
+    /**
+     * Encodes {@code value}.
+     *
+     * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
+     *     {@link Double#isInfinite() infinities}.
+     * @return this writer.
+     */
+    public JsonWriter value(double value) throws IOException {
+        if (Double.isNaN(value) || Double.isInfinite(value)) {
+            throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
+        }
+        beforeValue(false);
+        out.append(Double.toString(value));
+        return this;
+    }
+
+    /**
+     * Encodes {@code value}.
+     *
+     * @return this writer.
+     */
+    public JsonWriter value(long value) throws IOException {
+        beforeValue(false);
+        out.write(Long.toString(value));
+        return this;
+    }
+
+    /**
+     * Ensures all buffered data is written to the underlying {@link Writer}
+     * and flushes that writer.
+     */
+    public void flush() throws IOException {
+        out.flush();
+    }
+
+    /**
+     * Flushes and closes this writer and the underlying {@link Writer}.
+     *
+     * @throws IOException if the JSON document is incomplete.
+     */
+    public void close() throws IOException {
+        out.close();
+
+        if (peek() != JsonScope.NONEMPTY_DOCUMENT) {
+            throw new IOException("Incomplete document");
+        }
+    }
+
+    private void string(String value) throws IOException {
+        out.write("\"");
+        for (int i = 0, length = value.length(); i < length; i++) {
+            char c = value.charAt(i);
+
+            /*
+             * From RFC 4627, "All Unicode characters may be placed within the
+             * quotation marks except for the characters that must be escaped:
+             * quotation mark, reverse solidus, and the control characters
+             * (U+0000 through U+001F)."
+             */
+            switch (c) {
+                case '"':
+                case '\\':
+                case '/':
+                    out.write('\\');
+                    out.write(c);
+                    break;
+
+                case '\t':
+                    out.write("\\t");
+                    break;
+
+                case '\b':
+                    out.write("\\b");
+                    break;
+
+                case '\n':
+                    out.write("\\n");
+                    break;
+
+                case '\r':
+                    out.write("\\r");
+                    break;
+
+                case '\f':
+                    out.write("\\f");
+                    break;
+
+                default:
+                    if (c <= 0x1F) {
+                        out.write(String.format("\\u%04x", (int) c));
+                    } else {
+                        out.write(c);
+                    }
+                    break;
+            }
+
+        }
+        out.write("\"");
+    }
+
+    private void newline() throws IOException {
+        if (indent == null) {
+            return;
+        }
+
+        out.write("\n");
+        for (int i = 1; i < stack.size(); i++) {
+            out.write(indent);
+        }
+    }
+
+    /**
+     * Inserts any necessary separators and whitespace before a name. Also
+     * adjusts the stack to expect the name's value.
+     */
+    private void beforeName() throws IOException {
+        JsonScope context = peek();
+        if (context == JsonScope.NONEMPTY_OBJECT) { // first in object
+            out.write(',');
+        } else if (context != JsonScope.EMPTY_OBJECT) { // not in an object!
+            throw new IllegalStateException("Nesting problem: " + stack);
+        }
+        newline();
+        replaceTop(JsonScope.DANGLING_NAME);
+    }
+
+    /**
+     * Inserts any necessary separators and whitespace before a literal value,
+     * inline array, or inline object. Also adjusts the stack to expect either a
+     * closing bracket or another element.
+     *
+     * @param root true if the value is a new array or object, the two values
+     *     permitted as top-level elements.
+     */
+    private void beforeValue(boolean root) throws IOException {
+        switch (peek()) {
+            case EMPTY_DOCUMENT: // first in document
+                if (!root) {
+                    throw new IllegalStateException(
+                            "JSON must start with an array or an object.");
+                }
+                replaceTop(JsonScope.NONEMPTY_DOCUMENT);
+                break;
+
+            case EMPTY_ARRAY: // first in array
+                replaceTop(JsonScope.NONEMPTY_ARRAY);
+                newline();
+                break;
+
+            case NONEMPTY_ARRAY: // another in array
+                out.append(',');
+                newline();
+                break;
+
+            case DANGLING_NAME: // value for name
+                out.append(separator);
+                replaceTop(JsonScope.NONEMPTY_OBJECT);
+                break;
+
+            case NONEMPTY_DOCUMENT:
+                throw new IllegalStateException(
+                        "JSON must have only one top-level value.");
+
+            default:
+                throw new IllegalStateException("Nesting problem: " + stack);
+        }
+    }
+}
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 8e1338d..96bd884 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -40,7 +40,6 @@
 /**
  * An implementation of Canvas on top of OpenGL ES 2.0.
  */
-@SuppressWarnings({"deprecation"})
 class GLES20Canvas extends Canvas {
     @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
     private final GL mGl;
@@ -56,6 +55,17 @@
     private final Rect mClipBounds = new Rect();
 
     private DrawFilter mFilter;
+    
+    ///////////////////////////////////////////////////////////////////////////
+    // JNI
+    ///////////////////////////////////////////////////////////////////////////
+
+    private static native boolean nIsAvailable();
+    private static boolean sIsAvailable = nIsAvailable();
+
+    static boolean isAvailable() {
+        return sIsAvailable;
+    }
 
     ///////////////////////////////////////////////////////////////////////////
     // Constructors
@@ -91,11 +101,6 @@
     }
 
     @Override
-    public GL getGL() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
     public void setBitmap(Bitmap bitmap) {
         throw new UnsupportedOperationException();
     }
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 090a743..60d495f 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -28,10 +28,6 @@
 import javax.microedition.khronos.egl.EGLDisplay;
 import javax.microedition.khronos.egl.EGLSurface;
 import javax.microedition.khronos.opengles.GL;
-import javax.microedition.khronos.opengles.GL11;
-
-import static javax.microedition.khronos.opengles.GL10.GL_COLOR_BUFFER_BIT;
-import static javax.microedition.khronos.opengles.GL10.GL_SCISSOR_TEST;
 
 /**
  * Interface for rendering a ViewRoot using hardware acceleration.
@@ -110,10 +106,8 @@
      */
     static HardwareRenderer createGlRenderer(int glVersion, boolean translucent) {
         switch (glVersion) {
-            case 1:
-                return new Gl10Renderer(translucent);
             case 2:
-                return new Gl20Renderer(translucent);
+                return Gl20Renderer.create(translucent);
         }
         throw new IllegalArgumentException("Unknown GL version: " + glVersion);
     }
@@ -520,43 +514,13 @@
         @Override
         void onPreDraw() {
             mGlCanvas.onPreDraw();
-        }        
-    }
-
-    /**
-     * Hardware renderer using OpenGL ES 1.0.
-     */
-    @SuppressWarnings({"deprecation"})
-    static class Gl10Renderer extends GlRenderer {
-        Gl10Renderer(boolean translucent) {
-            super(1, translucent);
         }
 
-        @Override
-        Canvas createCanvas() {
-            return new Canvas(mGl);
-        }
-
-        @Override
-        void destroy() {
-            if (isEnabled()) {
-                nativeAbandonGlCaches();
+        static HardwareRenderer create(boolean translucent) {
+            if (GLES20Canvas.isAvailable()) {
+                return new Gl20Renderer(translucent);
             }
-
-            super.destroy();
-        }
-
-        @Override
-        void onPreDraw() {
-            GL11 gl = (GL11) mGl;
-            gl.glDisable(GL_SCISSOR_TEST);
-            gl.glClearColor(0, 0, 0, 0);
-            gl.glClear(GL_COLOR_BUFFER_BIT);
-            gl.glEnable(GL_SCISSOR_TEST);
+            return null;
         }
     }
-
-    // Inform Skia to just abandon its texture cache IDs doesn't call glDeleteTextures
-    // Used only by the native Skia OpenGL ES 1.x implementation
-    private static native void nativeAbandonGlCaches();
 }
diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java
index 25df1f4..fed55dc 100755
--- a/core/java/android/view/WindowOrientationListener.java
+++ b/core/java/android/view/WindowOrientationListener.java
@@ -68,7 +68,7 @@
         mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
         if (mSensor != null) {
             // Create listener only if sensors do exist
-            mSensorEventListener = new SensorEventListenerImpl();
+            mSensorEventListener = new SensorEventListenerImpl(this);
         }
     }
 
@@ -109,8 +109,35 @@
         }
         return -1;
     }
-    
-    class SensorEventListenerImpl implements SensorEventListener {
+
+    /**
+     * This class filters the raw accelerometer data and tries to detect actual changes in
+     * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters,
+     * but here's the outline:
+     *
+     *  - Convert the acceleromter vector from cartesian to spherical coordinates. Since we're
+     * dealing with rotation of the device, this is the sensible coordinate system to work in. The
+     * zenith direction is the Z-axis, i.e. the direction the screen is facing. The radial distance
+     * is referred to as the magnitude below. The elevation angle is referred to as the "tilt"
+     * below. The azimuth angle is referred to as the "orientation" below (and the azimuth axis is
+     * the Y-axis). See http://en.wikipedia.org/wiki/Spherical_coordinate_system for reference.
+     *
+     *  - Low-pass filter the tilt and orientation angles to avoid "twitchy" behavior.
+     *
+     *  - When the orientation angle reaches a certain threshold, transition to the corresponding
+     * orientation. These thresholds have some hysteresis built-in to avoid oscillation.
+     *
+     *  - Use the magnitude to judge the accuracy of the data. Under ideal conditions, the magnitude
+     * should equal to that of gravity. When it differs significantly, we know the device is under
+     * external acceleration and we can't trust the data.
+     *
+     *  - Use the tilt angle to judge the accuracy of orientation data. When the tilt angle is high
+     * in magnitude, we distrust the orientation data, because when the device is nearly flat, small
+     * physical movements produce large changes in orientation angle.
+     *
+     * Details are explained below.
+     */
+    static class SensorEventListenerImpl implements SensorEventListener {
         // We work with all angles in degrees in this class.
         private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI);
 
@@ -125,54 +152,50 @@
         private static final int ROTATION_90 = 1;
         private static final int ROTATION_270 = 2;
 
-        // Current orientation state
-        private int mRotation = ROTATION_0;
-
         // Mapping our internal aliases into actual Surface rotation values
-        private final int[] SURFACE_ROTATIONS = new int[] {Surface.ROTATION_0, Surface.ROTATION_90,
-                Surface.ROTATION_270};
+        private static final int[] SURFACE_ROTATIONS = new int[] {
+            Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270};
 
         // Threshold ranges of orientation angle to transition into other orientation states.
         // The first list is for transitions from ROTATION_0, the next for ROTATION_90, etc.
         // ROTATE_TO defines the orientation each threshold range transitions to, and must be kept
         // in sync with this.
-        // The thresholds are nearly regular -- we generally transition about the halfway point
-        // between two states with a swing of 30 degrees for hysteresis.  For ROTATION_180,
-        // however, we enforce stricter thresholds, pushing the thresholds 15 degrees closer to 180.
-        private final int[][][] THRESHOLDS = new int[][][] {
+        // We generally transition about the halfway point between two states with a swing of 30
+        // degrees for hysteresis.
+        private static final int[][][] THRESHOLDS = new int[][][] {
                 {{60, 180}, {180, 300}},
+                {{0, 30}, {195, 315}, {315, 360}},
                 {{0, 45}, {45, 165}, {330, 360}},
-                {{0, 30}, {195, 315}, {315, 360}}
         };
 
         // See THRESHOLDS
-        private final int[][] ROTATE_TO = new int[][] {
-                {ROTATION_270, ROTATION_90},
+        private static final int[][] ROTATE_TO = new int[][] {
+                {ROTATION_90, ROTATION_270},
                 {ROTATION_0, ROTATION_270, ROTATION_0},
-                {ROTATION_0, ROTATION_90, ROTATION_0}
+                {ROTATION_0, ROTATION_90, ROTATION_0},
         };
 
-        // Maximum absolute tilt angle at which to consider orientation changes.  Beyond this (i.e.
-        // when screen is facing the sky or ground), we refuse to make any orientation changes.
-        private static final int MAX_TILT = 65;
+        // Maximum absolute tilt angle at which to consider orientation data.  Beyond this (i.e.
+        // when screen is facing the sky or ground), we completely ignore orientation data.
+        private static final int MAX_TILT = 75;
 
         // Additional limits on tilt angle to transition to each new orientation.  We ignore all
-        // vectors with tilt beyond MAX_TILT, but we can set stricter limits on transition to a
+        // data with tilt beyond MAX_TILT, but we can set stricter limits on transitions to a
         // particular orientation here.
-        private final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, MAX_TILT, MAX_TILT};
+        private static final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, 65, 65};
 
         // Between this tilt angle and MAX_TILT, we'll allow orientation changes, but we'll filter
         // with a higher time constant, making us less sensitive to change.  This primarily helps
         // prevent momentary orientation changes when placing a device on a table from the side (or
         // picking one up).
-        private static final int PARTIAL_TILT = 45;
+        private static final int PARTIAL_TILT = 50;
 
         // Maximum allowable deviation of the magnitude of the sensor vector from that of gravity,
         // in m/s^2.  Beyond this, we assume the phone is under external forces and we can't trust
         // the sensor data.  However, under constantly vibrating conditions (think car mount), we
         // still want to pick up changes, so rather than ignore the data, we filter it with a very
         // high time constant.
-        private static final int MAX_DEVIATION_FROM_GRAVITY = 1;
+        private static final float MAX_DEVIATION_FROM_GRAVITY = 1.5f;
 
         // Actual sampling period corresponding to SensorManager.SENSOR_DELAY_NORMAL.  There's no
         // way to get this information from SensorManager.
@@ -185,28 +208,46 @@
         // background.
 
         // When device is near-vertical (screen approximately facing the horizon)
-        private static final int DEFAULT_TIME_CONSTANT_MS = 200;
+        private static final int DEFAULT_TIME_CONSTANT_MS = 50;
         // When device is partially tilted towards the sky or ground
-        private static final int TILTED_TIME_CONSTANT_MS = 600;
+        private static final int TILTED_TIME_CONSTANT_MS = 300;
         // When device is under external acceleration, i.e. not just gravity.  We heavily distrust
         // such readings.
-        private static final int ACCELERATING_TIME_CONSTANT_MS = 5000;
+        private static final int ACCELERATING_TIME_CONSTANT_MS = 2000;
 
         private static final float DEFAULT_LOWPASS_ALPHA =
-            (float) SAMPLING_PERIOD_MS / (DEFAULT_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS);
+            computeLowpassAlpha(DEFAULT_TIME_CONSTANT_MS);
         private static final float TILTED_LOWPASS_ALPHA =
-            (float) SAMPLING_PERIOD_MS / (TILTED_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS);
+            computeLowpassAlpha(TILTED_TIME_CONSTANT_MS);
         private static final float ACCELERATING_LOWPASS_ALPHA =
-            (float) SAMPLING_PERIOD_MS / (ACCELERATING_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS);
+            computeLowpassAlpha(ACCELERATING_TIME_CONSTANT_MS);
 
-        // The low-pass filtered accelerometer data
-        private float[] mFilteredVector = new float[] {0, 0, 0};
+        private WindowOrientationListener mOrientationListener;
+        private int mRotation = ROTATION_0; // Current orientation state
+        private float mTiltAngle = 0; // low-pass filtered
+        private float mOrientationAngle = 0; // low-pass filtered
+
+        /*
+         * Each "distrust" counter represents our current level of distrust in the data based on
+         * a certain signal.  For each data point that is deemed unreliable based on that signal,
+         * the counter increases; otherwise, the counter decreases.  Exact rules vary.
+         */
+        private int mAccelerationDistrust = 0; // based on magnitude != gravity
+        private int mTiltDistrust = 0; // based on tilt close to +/- 90 degrees
+
+        public SensorEventListenerImpl(WindowOrientationListener orientationListener) {
+            mOrientationListener = orientationListener;
+        }
+
+        private static float computeLowpassAlpha(int timeConstantMs) {
+            return (float) SAMPLING_PERIOD_MS / (timeConstantMs + SAMPLING_PERIOD_MS);
+        }
 
         int getCurrentRotation() {
             return SURFACE_ROTATIONS[mRotation];
         }
 
-        private void calculateNewRotation(int orientation, int tiltAngle) {
+        private void calculateNewRotation(float orientation, float tiltAngle) {
             if (localLOGV) Log.i(TAG, orientation + ", " + tiltAngle + ", " + mRotation);
             int thresholdRanges[][] = THRESHOLDS[mRotation];
             int row = -1;
@@ -226,7 +267,7 @@
 
             if (localLOGV) Log.i(TAG, " new rotation = " + rotation);
             mRotation = rotation;
-            onOrientationChanged(SURFACE_ROTATIONS[rotation]);
+            mOrientationListener.onOrientationChanged(getCurrentRotation());
         }
 
         private float lowpassFilter(float newValue, float oldValue, float alpha) {
@@ -238,11 +279,11 @@
         }
 
         /**
-         * Absolute angle between upVector and the x-y plane (the plane of the screen), in [0, 90].
-         * 90 degrees = screen facing the sky or ground.
+         * Angle between upVector and the x-y plane (the plane of the screen), in [-90, 90].
+         * +/- 90 degrees = screen facing the sky or ground.
          */
         private float tiltAngle(float z, float magnitude) {
-            return Math.abs((float) Math.asin(z / magnitude) * RADIANS_TO_DEGREES);
+            return (float) Math.asin(z / magnitude) * RADIANS_TO_DEGREES;
         }
 
         public void onSensorChanged(SensorEvent event) {
@@ -253,34 +294,124 @@
             float z = event.values[_DATA_Z];
             float magnitude = vectorMagnitude(x, y, z);
             float deviation = Math.abs(magnitude - SensorManager.STANDARD_GRAVITY);
-            float tiltAngle = tiltAngle(z, magnitude);
 
-            float alpha = DEFAULT_LOWPASS_ALPHA;
-            if (tiltAngle > MAX_TILT) {
-                return;
-            } else if (deviation > MAX_DEVIATION_FROM_GRAVITY) {
+            handleAccelerationDistrust(deviation);
+
+            // only filter tilt when we're accelerating
+            float alpha = 1;
+            if (mAccelerationDistrust > 0) {
                 alpha = ACCELERATING_LOWPASS_ALPHA;
-            } else if (tiltAngle > PARTIAL_TILT) {
+            }
+            float newTiltAngle = tiltAngle(z, magnitude);
+            mTiltAngle = lowpassFilter(newTiltAngle, mTiltAngle, alpha);
+
+            float absoluteTilt = Math.abs(mTiltAngle);
+            if (checkFullyTilted(absoluteTilt)) {
+                return; // when fully tilted, ignore orientation entirely
+            }
+
+            float newOrientationAngle = computeNewOrientation(x, y);
+            filterOrientation(absoluteTilt, newOrientationAngle);
+            calculateNewRotation(mOrientationAngle, absoluteTilt);
+        }
+
+        /**
+         * When accelerating, increment distrust; otherwise, decrement distrust.  The idea is that
+         * if a single jolt happens among otherwise good data, we should keep trusting the good
+         * data.  On the other hand, if a series of many bad readings comes in (as if the phone is
+         * being rapidly shaken), we should wait until things "settle down", i.e. we get a string
+         * of good readings.
+         *
+         * @param deviation absolute difference between the current magnitude and gravity
+         */
+        private void handleAccelerationDistrust(float deviation) {
+            if (deviation > MAX_DEVIATION_FROM_GRAVITY) {
+                if (mAccelerationDistrust < 5) {
+                    mAccelerationDistrust++;
+                }
+            } else if (mAccelerationDistrust > 0) {
+                mAccelerationDistrust--;
+            }
+        }
+
+        /**
+         * Check if the phone is tilted towards the sky or ground and handle that appropriately.
+         * When fully tilted, we automatically push the tilt up to a fixed value; otherwise we
+         * decrement it.  The idea is to distrust the first few readings after the phone gets
+         * un-tilted, no matter what, i.e. preventing an accidental transition when the phone is
+         * picked up from a table.
+         *
+         * We also reset the orientation angle to the center of the current screen orientation.
+         * Since there is no real orientation of the phone, we want to ignore the most recent sensor
+         * data and reset it to this value to avoid a premature transition when the phone starts to
+         * get un-tilted.
+         *
+         * @param absoluteTilt the absolute value of the current tilt angle
+         * @return true if the phone is fully tilted
+         */
+        private boolean checkFullyTilted(float absoluteTilt) {
+            boolean fullyTilted = absoluteTilt > MAX_TILT;
+            if (fullyTilted) {
+                if (mRotation == ROTATION_0) {
+                    mOrientationAngle = 0;
+                } else if (mRotation == ROTATION_90) {
+                    mOrientationAngle = 90;
+                } else { // ROTATION_270
+                    mOrientationAngle = 270;
+                }
+
+                if (mTiltDistrust < 3) {
+                    mTiltDistrust = 3;
+                }
+            } else if (mTiltDistrust > 0) {
+                mTiltDistrust--;
+            }
+            return fullyTilted;
+        }
+
+        /**
+         * Angle between the x-y projection of upVector and the +y-axis, increasing
+         * clockwise.
+         * 0 degrees = speaker end towards the sky
+         * 90 degrees = right edge of device towards the sky
+         */
+        private float computeNewOrientation(float x, float y) {
+            float orientationAngle = (float) -Math.atan2(-x, y) * RADIANS_TO_DEGREES;
+            // atan2 returns [-180, 180]; normalize to [0, 360]
+            if (orientationAngle < 0) {
+                orientationAngle += 360;
+            }
+            return orientationAngle;
+        }
+
+        /**
+         * Compute a new filtered orientation angle.
+         */
+        private void filterOrientation(float absoluteTilt, float orientationAngle) {
+            float alpha = DEFAULT_LOWPASS_ALPHA;
+            if (mTiltDistrust > 0 || mAccelerationDistrust > 1) {
+                // when fully tilted, or under more than a transient acceleration, distrust heavily
+                alpha = ACCELERATING_LOWPASS_ALPHA;
+            } else if (absoluteTilt > PARTIAL_TILT || mAccelerationDistrust == 1) {
+                // when tilted partway, or under transient acceleration, distrust lightly
                 alpha = TILTED_LOWPASS_ALPHA;
             }
 
-            x = mFilteredVector[0] = lowpassFilter(x, mFilteredVector[0], alpha);
-            y = mFilteredVector[1] = lowpassFilter(y, mFilteredVector[1], alpha);
-            z = mFilteredVector[2] = lowpassFilter(z, mFilteredVector[2], alpha);
-            magnitude = vectorMagnitude(x, y, z);
-            tiltAngle = tiltAngle(z, magnitude);
-
-            // Angle between the x-y projection of upVector and the +y-axis, increasing
-            // counter-clockwise.
-            // 0 degrees = speaker end towards the sky
-            // 90 degrees = left edge of device towards the sky
-            float orientationAngle = (float) Math.atan2(-x, y) * RADIANS_TO_DEGREES;
-            int orientation = Math.round(orientationAngle);
-            // atan2 returns (-180, 180]; normalize to [0, 360)
-            if (orientation < 0) {
-                orientation += 360;
+            // since we're lowpass filtering a value with periodic boundary conditions, we need to
+            // adjust the new value to filter in the right direction...
+            float deltaOrientation = orientationAngle - mOrientationAngle;
+            if (deltaOrientation > 180) {
+                orientationAngle -= 360;
+            } else if (deltaOrientation < -180) {
+                orientationAngle += 360;
             }
-            calculateNewRotation(orientation, Math.round(tiltAngle));
+            mOrientationAngle = lowpassFilter(orientationAngle, mOrientationAngle, alpha);
+            // ...and then adjust back to ensure we're in the range [0, 360]
+            if (mOrientationAngle > 360) {
+                mOrientationAngle -= 360;
+            } else if (mOrientationAngle < 0) {
+                mOrientationAngle += 360;
+            }
         }
 
         public void onAccuracyChanged(Sensor sensor, int accuracy) {
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 43a3e04..d1b0902 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -21,8 +21,8 @@
 import android.content.ClipboardManager;
 import android.content.Context;
 import android.content.DialogInterface;
-import android.content.Intent;
 import android.content.DialogInterface.OnCancelListener;
+import android.content.Intent;
 import android.database.DataSetObserver;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -43,7 +43,6 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.speech.tts.TextToSpeech;
 import android.text.Selection;
@@ -74,13 +73,15 @@
 import android.widget.AbsoluteLayout;
 import android.widget.Adapter;
 import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
 import android.widget.ArrayAdapter;
 import android.widget.CheckedTextView;
 import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.Scroller;
 import android.widget.Toast;
-import android.widget.AdapterView.OnItemClickListener;
+
+import junit.framework.Assert;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -95,8 +96,6 @@
 import java.util.Map;
 import java.util.Set;
 
-import junit.framework.Assert;
-
 /**
  * <p>A View that displays web pages. This class is the basis upon which you
  * can roll your own web browser or simply display some online content within your Activity.
@@ -291,7 +290,7 @@
  * property to {@code device-dpi}. This stops Android from performing scaling in your web page and
  * allows you to make the necessary adjustments for each density via CSS and JavaScript.</p>
  *
- * 
+ *
  */
 @Widget
 public class WebView extends AbsoluteLayout
@@ -1057,8 +1056,10 @@
 
     /*
      * Return the amount of the titlebarview (if any) that is visible
+     *
+     * @hide
      */
-    int getVisibleTitleHeight() {
+    public int getVisibleTitleHeight() {
         return Math.max(getTitleHeight() - mScrollY, 0);
     }
 
@@ -2695,6 +2696,16 @@
         mWebViewCore.sendMessage(EventHub.DOC_HAS_IMAGES, response);
     }
 
+    /**
+     * Request the scroller to abort any ongoing animation
+     *
+     * @hide
+     */
+    public void stopScroll() {
+        mScroller.forceFinished(true);
+        mLastVelocity = 0;
+    }
+
     @Override
     public void computeScroll() {
         if (mScroller.computeScrollOffset()) {
@@ -3751,6 +3762,19 @@
     private boolean mGotCenterDown = false;
 
     @Override
+    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+        // send complex characters to webkit for use by JS and plugins
+        if (keyCode == KeyEvent.KEYCODE_UNKNOWN && event.getCharacters() != null) {
+            // pass the key to DOM
+            mWebViewCore.sendMessage(EventHub.KEY_DOWN, event);
+            mWebViewCore.sendMessage(EventHub.KEY_UP, event);
+            // return true as DOM handles the key
+            return true;
+        }
+        return false;
+    }
+
+    @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         if (DebugFlags.WEB_VIEW) {
             Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis()
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 21af570..3c28c94 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -1627,9 +1627,16 @@
                     + evt);
         }
         int keyCode = evt.getKeyCode();
-        if (!nativeKey(keyCode, evt.getUnicodeChar(),
-                evt.getRepeatCount(), evt.isShiftPressed(), evt.isAltPressed(),
-                evt.isSymPressed(),
+        int unicodeChar = evt.getUnicodeChar();
+
+        if (keyCode == KeyEvent.KEYCODE_UNKNOWN && evt.getCharacters() != null
+                && evt.getCharacters().length() > 0) {
+            // we should only receive individual complex characters
+            unicodeChar = evt.getCharacters().codePointAt(0);
+        }
+
+        if (!nativeKey(keyCode, unicodeChar, evt.getRepeatCount(), evt.isShiftPressed(),
+                evt.isAltPressed(), evt.isSymPressed(),
                 isDown) && keyCode != KeyEvent.KEYCODE_ENTER) {
             if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
                     && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 24f14dc..f3f68f9 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -21,7 +21,9 @@
 
 import org.xmlpull.v1.XmlPullParserException;
 
+import android.content.ClippedData;
 import android.content.Context;
+import android.content.ClipboardManager;
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
@@ -34,6 +36,7 @@
 import android.graphics.RectF;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -42,7 +45,6 @@
 import android.os.ResultReceiver;
 import android.os.SystemClock;
 import android.text.BoringLayout;
-import android.text.ClipboardManager;
 import android.text.DynamicLayout;
 import android.text.Editable;
 import android.text.GetChars;
@@ -7061,7 +7063,7 @@
                 getSelectionStart() >= 0 &&
                 getSelectionEnd() >= 0 &&
                 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
-                hasText());
+                hasPrimaryClip());
     }
 
     /**
@@ -7270,7 +7272,7 @@
         int min = Math.max(0, Math.min(selStart, selEnd));
         int max = Math.max(0, Math.max(selStart, selEnd));
 
-        ClipboardManager clip = (ClipboardManager)getContext()
+        ClipboardManager clipboard = (ClipboardManager)getContext()
                 .getSystemService(Context.CLIPBOARD_SERVICE);
 
         switch (id) {
@@ -7278,8 +7280,18 @@
                 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
 
                 URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class);
-                if (urls.length == 1) {
-                    clip.setText(urls[0].getURL());
+                if (urls.length >= 1) {
+                    ClippedData clip = null;
+                    for (int i=0; i<urls.length; i++) {
+                        Uri uri = Uri.parse(urls[0].getURL());
+                        ClippedData.Item item = new ClippedData.Item(uri);
+                        if (clip == null) {
+                            clip = new ClippedData(null, null, item);
+                        } else {
+                            clip.addItem(item);
+                        }
+                    }
+                    clipboard.setPrimaryClip(clip);
                 }
                 return true;
 
@@ -7490,8 +7502,8 @@
                 return true;
             }
 
-            ClipboardManager clip = (ClipboardManager) getContext().
-                                                        getSystemService(Context.CLIPBOARD_SERVICE);
+            ClipboardManager clipboard = (ClipboardManager) getContext().
+                    getSystemService(Context.CLIPBOARD_SERVICE);
 
             int min = 0;
             int max = mText.length();
@@ -7506,23 +7518,36 @@
 
             switch (item.getItemId()) {
                 case ID_PASTE:
-                    CharSequence paste = clip.getText();
-
-                    if (paste != null) {
-                        Selection.setSelection((Spannable) mText, max);
-                        ((Editable) mText).replace(min, max, paste);
+                    ClippedData clip = clipboard.getPrimaryClip();
+                    if (clip != null) {
+                        boolean didfirst = false;
+                        for (int i=0; i<clip.getItemCount(); i++) {
+                            CharSequence paste = clip.getItem(i).coerceToText(getContext());
+                            if (paste != null) {
+                                if (!didfirst) {
+                                    Selection.setSelection((Spannable) mText, max);
+                                    ((Editable) mText).replace(min, max, paste);
+                                } else {
+                                    ((Editable) mText).insert(getSelectionEnd(), "\n");
+                                    ((Editable) mText).insert(getSelectionEnd(), paste);
+                                }
+                            }
+                        }
                         finishSelectionActionMode();
                     }
+
                     return true;
 
                 case ID_CUT:
-                    clip.setText(mTransformed.subSequence(min, max));
+                    clipboard.setPrimaryClip(new ClippedData(null, null,
+                            new ClippedData.Item(mTransformed.subSequence(min, max))));
                     ((Editable) mText).delete(min, max);
                     finishSelectionActionMode();
                     return true;
 
                 case ID_COPY:
-                    clip.setText(mTransformed.subSequence(min, max));
+                    clipboard.setPrimaryClip(new ClippedData(null, null,
+                            new ClippedData.Item(mTransformed.subSequence(min, max))));
                     finishSelectionActionMode();
                     return true;
             }
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index f8a77a1..9c84e0e 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -22,6 +22,7 @@
 import android.graphics.drawable.Drawable;
 import android.net.LocalServerSocket;
 import android.os.Debug;
+import android.os.FileUtils;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.Config;
@@ -506,6 +507,9 @@
 
         closeServerSocket();
 
+        // set umask to 0077 so new files and directories will default to owner-only permissions.
+        FileUtils.setUMask(FileUtils.S_IRWXG | FileUtils.S_IRWXO);
+
         /*
          * Pass the remaining arguments to SystemServer.
          * "--nice-name=system_server com.android.server.SystemServer"
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 77c77f9..d1a5ae14 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -19,6 +19,10 @@
   LOCAL_CFLAGS += -DCUSTOM_RUNTIME_HEAP_MAX=$(USE_CUSTOM_RUNTIME_HEAP_MAX)
 endif
 
+ifeq ($(USE_OPENGL_RENDERER),true)
+	LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER
+endif
+
 LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
 
 LOCAL_SRC_FILES:= \
@@ -47,7 +51,6 @@
 	android_view_InputChannel.cpp \
 	android_view_InputQueue.cpp \
 	android_view_KeyEvent.cpp \
-	android_view_HardwareRenderer.cpp \
 	android_view_GLES20Canvas.cpp \
 	android_view_MotionEvent.cpp \
 	android_text_AndroidCharacter.cpp \
@@ -170,7 +173,6 @@
 	libbinder \
 	libnetutils \
 	libui \
-	libhwui \
 	libgui \
 	libsurfaceflinger_client \
 	libcamera_client \
@@ -193,6 +195,10 @@
 	libwpa_client \
 	libjpeg
 
+ifeq ($(USE_OPENGL_RENDERER),true)
+	LOCAL_SHARED_LIBRARIES += libhwui
+endif
+
 ifeq ($(BOARD_HAVE_BLUETOOTH),true)
 LOCAL_C_INCLUDES += \
 	external/dbus \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 1e6d219..8b09e5f 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -115,7 +115,6 @@
 extern int register_com_android_internal_graphics_NativeUtils(JNIEnv *env);
 extern int register_android_view_Display(JNIEnv* env);
 extern int register_android_view_GLES20Canvas(JNIEnv* env);
-extern int register_android_view_HardwareRenderer(JNIEnv* env);
 extern int register_android_view_Surface(JNIEnv* env);
 extern int register_android_view_ViewRoot(JNIEnv* env);
 extern int register_android_database_CursorWindow(JNIEnv* env);
@@ -1244,7 +1243,6 @@
     REG_JNI(register_android_graphics_PixelFormat),
     REG_JNI(register_android_graphics_Graphics),
     REG_JNI(register_android_view_GLES20Canvas),
-    REG_JNI(register_android_view_HardwareRenderer),
     REG_JNI(register_android_view_Surface),
     REG_JNI(register_android_view_ViewRoot),
     REG_JNI(register_com_google_android_gles_jni_EGLImpl),
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index 558f5ff..bf150a9 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -20,7 +20,6 @@
 
 #include "SkCanvas.h"
 #include "SkDevice.h"
-#include "SkGLCanvas.h"
 #include "SkGraphics.h"
 #include "SkImageRef_GlobalPool.h"
 #include "SkPorterDuff.h"
@@ -67,13 +66,8 @@
         return bitmap ? new SkCanvas(*bitmap) : new SkCanvas;
     }
     
-    static SkCanvas* initGL(JNIEnv* env, jobject) {
-        return new SkGLCanvas;
-    }
-    
     static void freeCaches(JNIEnv* env, jobject) {
         // these are called in no particular order
-        SkGLCanvas::DeleteAllTextures();
         SkImageRef_GlobalPool::SetRAMUsed(0);
         SkGraphics::SetFontCacheUsed(0);
     }
@@ -110,11 +104,6 @@
         return canvas->getDevice()->accessBitmap(false).height();
     }
 
-    static void setViewport(JNIEnv* env, jobject, SkCanvas* canvas,
-                            int width, int height) {
-        canvas->setViewport(width, height);
-    }
-    
     static void setBitmap(JNIEnv* env, jobject, SkCanvas* canvas,
                           SkBitmap* bitmap) {
         canvas->setBitmapDevice(*bitmap);
@@ -880,12 +869,10 @@
 static JNINativeMethod gCanvasMethods[] = {
     {"finalizer", "(I)V", (void*) SkCanvasGlue::finalizer},
     {"initRaster","(I)I", (void*) SkCanvasGlue::initRaster},
-    {"initGL","()I", (void*) SkCanvasGlue::initGL},
     {"isOpaque","()Z", (void*) SkCanvasGlue::isOpaque},
     {"getWidth","()I", (void*) SkCanvasGlue::getWidth},
     {"getHeight","()I", (void*) SkCanvasGlue::getHeight},
     {"native_setBitmap","(II)V", (void*) SkCanvasGlue::setBitmap},
-    {"nativeSetViewport", "(III)V", (void*) SkCanvasGlue::setViewport},
     {"save","()I", (void*) SkCanvasGlue::saveAll},
     {"save","(I)I", (void*) SkCanvasGlue::save},
     {"native_saveLayer","(ILandroid/graphics/RectF;II)I",
diff --git a/core/jni/android/graphics/ColorFilter.cpp b/core/jni/android/graphics/ColorFilter.cpp
index 848234f..f3be8b0 100644
--- a/core/jni/android/graphics/ColorFilter.cpp
+++ b/core/jni/android/graphics/ColorFilter.cpp
@@ -36,40 +36,25 @@
         obj->safeUnref();
     }
 
-    static SkColorFilter* CreatePorterDuffFilter(JNIEnv* env, jobject, jint srcColor,
-            SkPorterDuff::Mode mode) {
-        return SkColorFilter::CreateModeFilter(srcColor, SkPorterDuff::ToXfermodeMode(mode));
-    }
- 
     static SkiaColorFilter* glCreatePorterDuffFilter(JNIEnv* env, jobject, jint srcColor,
             SkPorterDuff::Mode mode) {
+#ifdef USE_OPENGL_RENDERER
         return new SkiaBlendFilter(srcColor, SkPorterDuff::ToXfermodeMode(mode));
-    }
-
-    static SkColorFilter* CreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
-        return SkColorFilter::CreateLightingFilter(mul, add);
-    }
-    
-    static SkiaColorFilter* glCreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
-        return new SkiaLightingFilter(mul, add);
-    }
-
-    static SkColorFilter* CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
-        AutoJavaFloatArray autoArray(env, jarray, 20);
-        const float* src = autoArray.ptr();
-
-#ifdef SK_SCALAR_IS_FIXED
-        SkFixed array[20];
-        for (int i = 0; i < 20; i++) {
-            array[i] = SkFloatToScalar(src[i]);
-        }
-        return new SkColorMatrixFilter(array);
 #else
-        return new SkColorMatrixFilter(src);
+        return NULL;
+#endif
+    }
+
+    static SkiaColorFilter* glCreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
+#ifdef USE_OPENGL_RENDERER
+        return new SkiaLightingFilter(mul, add);
+#else
+        return NULL;
 #endif
     }
 
     static SkiaColorFilter* glCreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
+#ifdef USE_OPENGL_RENDERER
         AutoJavaFloatArray autoArray(env, jarray, 20);
         const float* src = autoArray.ptr();
 
@@ -86,6 +71,33 @@
         colorVector[3] = src[19];
 
         return new SkiaColorMatrixFilter(colorMatrix, colorVector);
+#else
+        return NULL;
+#endif
+    }
+
+    static SkColorFilter* CreatePorterDuffFilter(JNIEnv* env, jobject, jint srcColor,
+            SkPorterDuff::Mode mode) {
+        return SkColorFilter::CreateModeFilter(srcColor, SkPorterDuff::ToXfermodeMode(mode));
+    }
+
+    static SkColorFilter* CreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
+        return SkColorFilter::CreateLightingFilter(mul, add);
+    }
+
+    static SkColorFilter* CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
+        AutoJavaFloatArray autoArray(env, jarray, 20);
+        const float* src = autoArray.ptr();
+
+#ifdef SK_SCALAR_IS_FIXED
+        SkFixed array[20];
+        for (int i = 0; i < 20; i++) {
+            array[i] = SkFloatToScalar(src[i]);
+        }
+        return new SkColorMatrixFilter(array);
+#else
+        return new SkColorMatrixFilter(src);
+#endif
     }
 };
 
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index 34b4ab5..2b98e89 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -71,7 +71,9 @@
         else {
             shader->setLocalMatrix(*matrix);
         }
+#ifdef USE_OPENGL_RENDERER
         skiaShader->setMatrix(const_cast<SkMatrix*>(matrix));
+#endif
     }
 }
 
@@ -90,10 +92,14 @@
 
 static SkiaShader* BitmapShader_postConstructor(JNIEnv* env, jobject o, SkShader* shader,
         SkBitmap* bitmap, int tileModeX, int tileModeY) {
+#ifdef USE_OPENGL_RENDERER
     SkiaShader* skiaShader = new SkiaBitmapShader(bitmap, shader,
             static_cast<SkShader::TileMode>(tileModeX), static_cast<SkShader::TileMode>(tileModeY),
             NULL, (shader->getFlags() & SkShader::kOpaqueAlpha_Flag) == 0);
     return skiaShader;
+#else
+    return NULL;
+#endif
 }
     
 ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -134,7 +140,7 @@
 static SkiaShader* LinearGradient_postCreate1(JNIEnv* env, jobject o, SkShader* shader,
         float x0, float y0, float x1, float y1, jintArray colorArray,
         jfloatArray posArray, int tileMode) {
-
+#ifdef USE_OPENGL_RENDERER
     size_t count = env->GetArrayLength(colorArray);
     const jint* colorValues = env->GetIntArrayElements(colorArray, NULL);
 
@@ -162,10 +168,14 @@
 
     env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues), JNI_ABORT);
     return skiaShader;
+#else
+    return NULL;
+#endif
 }
 
 static SkiaShader* LinearGradient_postCreate2(JNIEnv* env, jobject o, SkShader* shader,
         float x0, float y0, float x1, float y1, int color0, int color1, int tileMode) {
+#ifdef USE_OPENGL_RENDERER
     float* storedBounds = new float[4];
     storedBounds[0] = x0; storedBounds[1] = y0;
     storedBounds[2] = x1; storedBounds[3] = y1;
@@ -183,6 +193,9 @@
             (shader->getFlags() & SkShader::kOpaqueAlpha_Flag) == 0);
 
     return skiaShader;
+#else
+    return NULL;
+#endif
 }
 
 static SkShader* LinearGradient_create2(JNIEnv* env, jobject o,
@@ -315,6 +328,7 @@
 
 static SkiaShader* ComposeShader_postCreate2(JNIEnv* env, jobject o, SkShader* shader,
         SkiaShader* shaderA, SkiaShader* shaderB, SkPorterDuff::Mode porterDuffMode) {
+#ifdef USE_OPENGL_RENDERER
     SkAutoUnref au(SkPorterDuff::CreateXfermode(porterDuffMode));
     SkXfermode* mode = (SkXfermode*) au.get();
     SkXfermode::Mode skiaMode;
@@ -322,15 +336,22 @@
         skiaMode = SkXfermode::kSrcOver_Mode;
     }
     return new SkiaComposeShader(shaderA, shaderB, skiaMode, shader);
+#else
+    return NULL;
+#endif
 }
 
 static SkiaShader* ComposeShader_postCreate1(JNIEnv* env, jobject o, SkShader* shader,
         SkiaShader* shaderA, SkiaShader* shaderB, SkXfermode* mode) {
+#ifdef USE_OPENGL_RENDERER
     SkXfermode::Mode skiaMode;
     if (!SkXfermode::IsMode(mode, &skiaMode)) {
         skiaMode = SkXfermode::kSrcOver_Mode;
     }
     return new SkiaComposeShader(shaderA, shaderB, skiaMode, shader);
+#else
+    return NULL;
+#endif
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/jni/android_os_FileUtils.cpp b/core/jni/android_os_FileUtils.cpp
index 21cb919..d3faa2f 100644
--- a/core/jni/android_os_FileUtils.cpp
+++ b/core/jni/android_os_FileUtils.cpp
@@ -113,6 +113,11 @@
     #endif
 }
 
+jint android_os_FileUtils_setUMask(JNIEnv* env, jobject clazz, jint mask)
+{
+    return umask(mask);
+}
+
 jint android_os_FileUtils_getFatVolumeId(JNIEnv* env, jobject clazz, jstring path)
 {
     #if HAVE_ANDROID_OS
@@ -170,6 +175,7 @@
 static const JNINativeMethod methods[] = {
     {"setPermissions",  "(Ljava/lang/String;III)I", (void*)android_os_FileUtils_setPermissions},
     {"getPermissions",  "(Ljava/lang/String;[I)I", (void*)android_os_FileUtils_getPermissions},
+    {"setUMask",        "(I)I",                    (void*)android_os_FileUtils_setUMask},
     {"getFatVolumeId",  "(Ljava/lang/String;)I", (void*)android_os_FileUtils_getFatVolumeId},
     {"getFileStatus",  "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z", (void*)android_os_FileUtils_getFileStatus},
 };
diff --git a/core/jni/android_os_ParcelFileDescriptor.cpp b/core/jni/android_os_ParcelFileDescriptor.cpp
index 848a57a..eceef1c 100644
--- a/core/jni/android_os_ParcelFileDescriptor.cpp
+++ b/core/jni/android_os_ParcelFileDescriptor.cpp
@@ -66,6 +66,26 @@
     return fileDescriptorClone;
 }
 
+static int android_os_ParcelFileDescriptor_createPipeNative(JNIEnv* env,
+    jobject clazz, jobjectArray outFds)
+{
+    int fds[2];
+    if (pipe(fds) < 0) {
+        return -errno;
+    }
+
+    for (int i=0; i<2; i++) {
+        jobject fdObj = env->NewObject(gFileDescriptorOffsets.mClass,
+                gFileDescriptorOffsets.mConstructor);
+        if (fdObj != NULL) {
+            env->SetIntField(fdObj, gFileDescriptorOffsets.mDescriptor, fds[i]);
+        }
+        env->SetObjectArrayElement(outFds, i, fdObj);
+    }
+
+    return 0;
+}
+
 static jint getFd(JNIEnv* env, jobject clazz)
 {
     jobject descriptor = env->GetObjectField(clazz, gParcelFileDescriptorOffsets.mFileDescriptor);
@@ -109,6 +129,8 @@
 static const JNINativeMethod gParcelFileDescriptorMethods[] = {
     {"getFileDescriptorFromSocket", "(Ljava/net/Socket;)Ljava/io/FileDescriptor;",
         (void*)android_os_ParcelFileDescriptor_getFileDescriptorFromSocket},
+    {"createPipeNative", "([Ljava/io/FileDescriptor;)I",
+        (void*)android_os_ParcelFileDescriptor_createPipeNative},
     {"getStatSize", "()J",
         (void*)android_os_ParcelFileDescriptor_getStatSize},
     {"seekTo", "(J)J",
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index 9fd2b86..bb1a9e3 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -41,6 +41,13 @@
 
 using namespace uirenderer;
 
+/**
+ * Note: OpenGLRenderer JNI layer is generated and compiled only on supported
+ *       devices. This means all the logic must be compiled only when the
+ *       preprocessor variable USE_OPENGL_RENDERER is defined.
+ */
+#ifdef USE_OPENGL_RENDERER
+
 // ----------------------------------------------------------------------------
 // Java APIs
 // ----------------------------------------------------------------------------
@@ -284,6 +291,20 @@
     env->ReleaseStringChars(text, textArray);
 }
 
+#endif // USE_OPENGL_RENDERER
+
+// ----------------------------------------------------------------------------
+// Common
+// ----------------------------------------------------------------------------
+
+static jboolean android_view_GLES20Canvas_isAvailable(JNIEnv* env, jobject clazz) {
+#ifdef USE_OPENGL_RENDERER
+    return JNI_TRUE;
+#else
+    return JNI_FALSE;
+#endif
+}
+
 // ----------------------------------------------------------------------------
 // JNI Glue
 // ----------------------------------------------------------------------------
@@ -291,6 +312,9 @@
 const char* const kClassPathName = "android/view/GLES20Canvas";
 
 static JNINativeMethod gMethods[] = {
+    {   "nIsAvailable",       "()Z",             (void*) android_view_GLES20Canvas_isAvailable },
+#ifdef USE_OPENGL_RENDERER
+
     {   "nCreateRenderer",    "()I",             (void*) android_view_GLES20Canvas_createRenderer },
     {   "nDestroyRenderer",   "(I)V",            (void*) android_view_GLES20Canvas_destroyRenderer },
     {   "nSetViewport",       "(III)V",          (void*) android_view_GLES20Canvas_setViewport },
@@ -334,16 +358,22 @@
 
     {   "nGetClipBounds",     "(ILandroid/graphics/Rect;)Z",
             (void*) android_view_GLES20Canvas_getClipBounds },
+#endif
 };
 
-#define FIND_CLASS(var, className) \
-        var = env->FindClass(className); \
-        LOG_FATAL_IF(! var, "Unable to find class " className); \
-        var = jclass(env->NewGlobalRef(var));
-
-#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
-        var = env->GetMethodID(clazz, methodName, methodDescriptor); \
-        LOG_FATAL_IF(! var, "Unable to find method " methodName);
+#ifdef USE_OPENGL_RENDERER
+    #define FIND_CLASS(var, className) \
+            var = env->FindClass(className); \
+            LOG_FATAL_IF(! var, "Unable to find class " className); \
+            var = jclass(env->NewGlobalRef(var));
+    
+    #define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+            var = env->GetMethodID(clazz, methodName, methodDescriptor); \
+            LOG_FATAL_IF(! var, "Unable to find method " methodName);
+#else
+    #define FIND_CLASS(var, className)
+    #define GET_METHOD_ID(var, clazz, methodName, methodDescriptor)
+#endif
 
 int register_android_view_GLES20Canvas(JNIEnv* env) {
     FIND_CLASS(gRectClassInfo.clazz, "android/graphics/Rect");
diff --git a/core/jni/android_view_HardwareRenderer.cpp b/core/jni/android_view_HardwareRenderer.cpp
deleted file mode 100644
index 6d20c9d..0000000
--- a/core/jni/android_view_HardwareRenderer.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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 <utils/SkGLCanvas.h>
-
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include <android_runtime/AndroidRuntime.h>
-#include <utils/misc.h>
-
-// ----------------------------------------------------------------------------
-
-namespace android {
-
-static void android_view_HardwareRenderer_abandonGlCaches(JNIEnv* env, jobject) {
-    SkGLCanvas::AbandonAllTextures();
-}
-
-// ----------------------------------------------------------------------------
-
-const char* const kClassPathName = "android/view/HardwareRenderer";
-
-static JNINativeMethod gMethods[] = {
-    {   "nativeAbandonGlCaches", "()V", (void*)android_view_HardwareRenderer_abandonGlCaches },
-};
-
-int register_android_view_HardwareRenderer(JNIEnv* env) {
-    return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
-}
-
-};
diff --git a/core/tests/coretests/src/android/util/JsonReaderTest.java b/core/tests/coretests/src/android/util/JsonReaderTest.java
new file mode 100644
index 0000000..ced9310
--- /dev/null
+++ b/core/tests/coretests/src/android/util/JsonReaderTest.java
@@ -0,0 +1,683 @@
+/*
+ * 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.util;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+public final class JsonReaderTest extends TestCase {
+
+    public void testReadArray() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[true, true]"));
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+        assertEquals(true, reader.nextBoolean());
+        reader.endArray();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testReadEmptyArray() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[]"));
+        reader.beginArray();
+        assertFalse(reader.hasNext());
+        reader.endArray();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testReadObject() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader(
+                "{\"a\": \"android\", \"b\": \"banana\"}"));
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+        assertEquals("android", reader.nextString());
+        assertEquals("b", reader.nextName());
+        assertEquals("banana", reader.nextString());
+        reader.endObject();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testReadEmptyObject() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{}"));
+        reader.beginObject();
+        assertFalse(reader.hasNext());
+        reader.endObject();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testSkipObject() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader(
+                "{\"a\": { \"c\": [], \"d\": [true, true, {}] }, \"b\": \"banana\"}"));
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+        reader.skipValue();
+        assertEquals("b", reader.nextName());
+        reader.skipValue();
+        reader.endObject();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testHelloWorld() throws IOException {
+        String json = "{\n" +
+                "   \"hello\": true,\n" +
+                "   \"foo\": [\"world\"]\n" +
+                "}";
+        JsonReader reader = new JsonReader(new StringReader(json));
+        reader.beginObject();
+        assertEquals("hello", reader.nextName());
+        assertEquals(true, reader.nextBoolean());
+        assertEquals("foo", reader.nextName());
+        reader.beginArray();
+        assertEquals("world", reader.nextString());
+        reader.endArray();
+        reader.endObject();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testNulls() {
+        try {
+            new JsonReader(null);
+            fail();
+        } catch (NullPointerException expected) {
+        }
+    }
+
+    public void testEmptyString() throws IOException {
+        try {
+            new JsonReader(new StringReader("")).beginArray();
+        } catch (IOException expected) {
+        }
+        try {
+            new JsonReader(new StringReader("")).beginObject();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testNoTopLevelObject() throws IOException {
+        try {
+            new JsonReader(new StringReader("true")).nextBoolean();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testCharacterUnescaping() throws IOException {
+        String json = "[\"a\","
+                + "\"a\\\"\","
+                + "\"\\\"\","
+                + "\":\","
+                + "\",\","
+                + "\"\\b\","
+                + "\"\\f\","
+                + "\"\\n\","
+                + "\"\\r\","
+                + "\"\\t\","
+                + "\" \","
+                + "\"\\\\\","
+                + "\"{\","
+                + "\"}\","
+                + "\"[\","
+                + "\"]\","
+                + "\"\\u0000\","
+                + "\"\\u0019\","
+                + "\"\\u20AC\""
+                + "]";
+        JsonReader reader = new JsonReader(new StringReader(json));
+        reader.beginArray();
+        assertEquals("a", reader.nextString());
+        assertEquals("a\"", reader.nextString());
+        assertEquals("\"", reader.nextString());
+        assertEquals(":", reader.nextString());
+        assertEquals(",", reader.nextString());
+        assertEquals("\b", reader.nextString());
+        assertEquals("\f", reader.nextString());
+        assertEquals("\n", reader.nextString());
+        assertEquals("\r", reader.nextString());
+        assertEquals("\t", reader.nextString());
+        assertEquals(" ", reader.nextString());
+        assertEquals("\\", reader.nextString());
+        assertEquals("{", reader.nextString());
+        assertEquals("}", reader.nextString());
+        assertEquals("[", reader.nextString());
+        assertEquals("]", reader.nextString());
+        assertEquals("\0", reader.nextString());
+        assertEquals("\u0019", reader.nextString());
+        assertEquals("\u20AC", reader.nextString());
+        reader.endArray();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testIntegersWithFractionalPartSpecified() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[1.0,1.0,1.0]"));
+        reader.beginArray();
+        assertEquals(1.0, reader.nextDouble());
+        assertEquals(1, reader.nextInt());
+        assertEquals(1L, reader.nextLong());
+    }
+
+    public void testDoubles() throws IOException {
+        String json = "[-0.0,"
+                + "1.0,"
+                + "1.7976931348623157E308,"
+                + "4.9E-324,"
+                + "0.0,"
+                + "-0.5,"
+                + "2.2250738585072014E-308,"
+                + "3.141592653589793,"
+                + "2.718281828459045]";
+        JsonReader reader = new JsonReader(new StringReader(json));
+        reader.beginArray();
+        assertEquals(-0.0, reader.nextDouble());
+        assertEquals(1.0, reader.nextDouble());
+        assertEquals(1.7976931348623157E308, reader.nextDouble());
+        assertEquals(4.9E-324, reader.nextDouble());
+        assertEquals(0.0, reader.nextDouble());
+        assertEquals(-0.5, reader.nextDouble());
+        assertEquals(2.2250738585072014E-308, reader.nextDouble());
+        assertEquals(3.141592653589793, reader.nextDouble());
+        assertEquals(2.718281828459045, reader.nextDouble());
+        reader.endArray();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testNonFiniteDoubles() throws IOException {
+        String json = "[NaN]";
+        JsonReader reader = new JsonReader(new StringReader(json));
+        reader.beginArray();
+        try {
+            reader.nextDouble();
+            fail();
+        } catch (NumberFormatException expected) {
+        }
+    }
+
+    public void testLongs() throws IOException {
+        String json = "[0,0,0,"
+                + "1,1,1,"
+                + "-1,-1,-1,"
+                + "-9223372036854775808,"
+                + "9223372036854775807]";
+        JsonReader reader = new JsonReader(new StringReader(json));
+        reader.beginArray();
+        assertEquals(0L, reader.nextLong());
+        assertEquals(0, reader.nextInt());
+        assertEquals(0.0, reader.nextDouble());
+        assertEquals(1L, reader.nextLong());
+        assertEquals(1, reader.nextInt());
+        assertEquals(1.0, reader.nextDouble());
+        assertEquals(-1L, reader.nextLong());
+        assertEquals(-1, reader.nextInt());
+        assertEquals(-1.0, reader.nextDouble());
+        try {
+            reader.nextInt();
+            fail();
+        } catch (NumberFormatException expected) {
+        }
+        assertEquals(Long.MIN_VALUE, reader.nextLong());
+        try {
+            reader.nextInt();
+            fail();
+        } catch (NumberFormatException expected) {
+        }
+        assertEquals(Long.MAX_VALUE, reader.nextLong());
+        reader.endArray();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    /**
+     * This test fails because there's no double for 9223372036854775806, and
+     * our long parsing uses Double.parseDouble() for fractional values.
+     */
+    public void testHighPrecisionLong() throws IOException {
+        String json = "[9223372036854775806.000]";
+        JsonReader reader = new JsonReader(new StringReader(json));
+        reader.beginArray();
+        assertEquals(9223372036854775806L, reader.nextLong());
+        reader.endArray();
+    }
+
+    public void testNumberWithOctalPrefix() throws IOException {
+        String json = "[01]";
+        JsonReader reader = new JsonReader(new StringReader(json));
+        reader.beginArray();
+        try {
+            reader.nextInt();
+            fail();
+        } catch (NumberFormatException expected) {
+        }
+        try {
+            reader.nextLong();
+            fail();
+        } catch (NumberFormatException expected) {
+        }
+        try {
+            reader.nextDouble();
+            fail();
+        } catch (NumberFormatException expected) {
+        }
+        assertEquals("01", reader.nextString());
+        reader.endArray();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testBooleans() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[true,false]"));
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+        assertEquals(false, reader.nextBoolean());
+        reader.endArray();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testMixedCaseLiterals() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[True,TruE,False,FALSE,NULL,nulL]"));
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+        assertEquals(true, reader.nextBoolean());
+        assertEquals(false, reader.nextBoolean());
+        assertEquals(false, reader.nextBoolean());
+        reader.nextNull();
+        reader.nextNull();
+        reader.endArray();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testMissingValue() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{\"a\":}"));
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+        try {
+            reader.nextString();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testPrematureEndOfInput() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{\"a\":true,"));
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+        assertEquals(true, reader.nextBoolean());
+        try {
+            reader.nextName();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testPrematurelyClosed() throws IOException {
+        try {
+            JsonReader reader = new JsonReader(new StringReader("{\"a\":[]}"));
+            reader.beginObject();
+            reader.close();
+            reader.nextName();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+
+        try {
+            JsonReader reader = new JsonReader(new StringReader("{\"a\":[]}"));
+            reader.close();
+            reader.beginObject();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+
+        try {
+            JsonReader reader = new JsonReader(new StringReader("{\"a\":true}"));
+            reader.beginObject();
+            reader.nextName();
+            reader.peek();
+            reader.close();
+            reader.nextBoolean();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testNextFailuresDoNotAdvance() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{\"a\":true}"));
+        reader.beginObject();
+        try {
+            reader.nextString();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        assertEquals("a", reader.nextName());
+        try {
+            reader.nextName();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            reader.beginArray();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            reader.endArray();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            reader.beginObject();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            reader.endObject();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        assertEquals(true, reader.nextBoolean());
+        try {
+            reader.nextString();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            reader.nextName();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            reader.beginArray();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            reader.endArray();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        reader.endObject();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+        reader.close();
+    }
+
+    public void testStringNullIsNotNull() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[\"null\"]"));
+        reader.beginArray();
+        try {
+            reader.nextNull();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testNullLiteralIsNotAString() throws IOException {
+       JsonReader reader = new JsonReader(new StringReader("[null]"));
+        reader.beginArray();
+        try {
+            reader.nextString();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testStrictNameValueSeparator() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{\"a\"=true}"));
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+        try {
+            reader.nextBoolean();
+            fail();
+        } catch (IOException expected) {
+        }
+
+        reader = new JsonReader(new StringReader("{\"a\"=>true}"));
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+        try {
+            reader.nextBoolean();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testLenientNameValueSeparator() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{\"a\"=true}"));
+        reader.setLenient(true);
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+        assertEquals(true, reader.nextBoolean());
+
+        reader = new JsonReader(new StringReader("{\"a\"=>true}"));
+        reader.setLenient(true);
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+        assertEquals(true, reader.nextBoolean());
+    }
+
+    public void testStrictComments() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[// comment \n true]"));
+        reader.beginArray();
+        try {
+            reader.nextBoolean();
+            fail();
+        } catch (IOException expected) {
+        }
+
+        reader = new JsonReader(new StringReader("[# comment \n true]"));
+        reader.beginArray();
+        try {
+            reader.nextBoolean();
+            fail();
+        } catch (IOException expected) {
+        }
+
+        reader = new JsonReader(new StringReader("[/* comment */ true]"));
+        reader.beginArray();
+        try {
+            reader.nextBoolean();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testLenientComments() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[// comment \n true]"));
+        reader.setLenient(true);
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+
+        reader = new JsonReader(new StringReader("[# comment \n true]"));
+        reader.setLenient(true);
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+
+        reader = new JsonReader(new StringReader("[/* comment */ true]"));
+        reader.setLenient(true);
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+    }
+
+    public void testStrictUnquotedNames() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{a:true}"));
+        reader.beginObject();
+        try {
+            reader.nextName();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testLenientUnquotedNames() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{a:true}"));
+        reader.setLenient(true);
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+    }
+
+    public void testStrictSingleQuotedNames() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{'a':true}"));
+        reader.beginObject();
+        try {
+            reader.nextName();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testLenientSingleQuotedNames() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{'a':true}"));
+        reader.setLenient(true);
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+    }
+
+    public void testStrictUnquotedStrings() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[a]"));
+        reader.beginArray();
+        try {
+            reader.nextString();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testLenientUnquotedStrings() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[a]"));
+        reader.setLenient(true);
+        reader.beginArray();
+        assertEquals("a", reader.nextString());
+    }
+
+    public void testStrictSingleQuotedStrings() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("['a']"));
+        reader.beginArray();
+        try {
+            reader.nextString();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testLenientSingleQuotedStrings() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("['a']"));
+        reader.setLenient(true);
+        reader.beginArray();
+        assertEquals("a", reader.nextString());
+    }
+
+    public void testStrictSemicolonDelimitedArray() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[true;true]"));
+        reader.beginArray();
+        try {
+            reader.nextBoolean();
+            reader.nextBoolean();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testLenientSemicolonDelimitedArray() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[true;true]"));
+        reader.setLenient(true);
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+        assertEquals(true, reader.nextBoolean());
+    }
+
+    public void testStrictSemicolonDelimitedNameValuePair() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{\"a\":true;\"b\":true}"));
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+        try {
+            reader.nextBoolean();
+            reader.nextName();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testLenientSemicolonDelimitedNameValuePair() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{\"a\":true;\"b\":true}"));
+        reader.setLenient(true);
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+        assertEquals(true, reader.nextBoolean());
+        assertEquals("b", reader.nextName());
+    }
+
+    public void testStrictUnnecessaryArraySeparators() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[true,,true]"));
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+        try {
+            reader.nextNull();
+            fail();
+        } catch (IOException expected) {
+        }
+
+        reader = new JsonReader(new StringReader("[,true]"));
+        reader.beginArray();
+        try {
+            reader.nextNull();
+            fail();
+        } catch (IOException expected) {
+        }
+
+        reader = new JsonReader(new StringReader("[true,]"));
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+        try {
+            reader.nextNull();
+            fail();
+        } catch (IOException expected) {
+        }
+
+        reader = new JsonReader(new StringReader("[,]"));
+        reader.beginArray();
+        try {
+            reader.nextNull();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testLenientUnnecessaryArraySeparators() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[true,,true]"));
+        reader.setLenient(true);
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+        reader.nextNull();
+        assertEquals(true, reader.nextBoolean());
+        reader.endArray();
+
+        reader = new JsonReader(new StringReader("[,true]"));
+        reader.setLenient(true);
+        reader.beginArray();
+        reader.nextNull();
+        assertEquals(true, reader.nextBoolean());
+        reader.endArray();
+
+        reader = new JsonReader(new StringReader("[true,]"));
+        reader.setLenient(true);
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+        reader.nextNull();
+        reader.endArray();
+
+        reader = new JsonReader(new StringReader("[,]"));
+        reader.setLenient(true);
+        reader.beginArray();
+        reader.nextNull();
+        reader.nextNull();
+        reader.endArray();
+    }
+}
diff --git a/core/tests/coretests/src/android/util/JsonWriterTest.java b/core/tests/coretests/src/android/util/JsonWriterTest.java
new file mode 100644
index 0000000..fa84023
--- /dev/null
+++ b/core/tests/coretests/src/android/util/JsonWriterTest.java
@@ -0,0 +1,419 @@
+/*
+ * 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.util;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+public final class JsonWriterTest extends TestCase {
+
+    public void testWrongTopLevelType() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        try {
+            jsonWriter.value("a");
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testTwoNames() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginObject();
+        jsonWriter.name("a");
+        try {
+            jsonWriter.name("a");
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testNameWithoutValue() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginObject();
+        jsonWriter.name("a");
+        try {
+            jsonWriter.endObject();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testValueWithoutName() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginObject();
+        try {
+            jsonWriter.value(true);
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testMultipleTopLevelValues() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray().endArray();
+        try {
+            jsonWriter.beginArray();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testBadNestingObject() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        jsonWriter.beginObject();
+        try {
+            jsonWriter.endArray();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testBadNestingArray() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        jsonWriter.beginArray();
+        try {
+            jsonWriter.endObject();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testNullName() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginObject();
+        try {
+            jsonWriter.name(null);
+            fail();
+        } catch (NullPointerException expected) {
+        }
+    }
+
+    public void testNullStringValue() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginObject();
+        jsonWriter.name("a");
+        jsonWriter.value(null);
+        jsonWriter.endObject();
+        assertEquals("{\"a\":null}", stringWriter.toString());
+    }
+
+    public void testNonFiniteDoubles() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        try {
+            jsonWriter.value(Double.NaN);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            jsonWriter.value(Double.NEGATIVE_INFINITY);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            jsonWriter.value(Double.POSITIVE_INFINITY);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testDoubles() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        jsonWriter.value(-0.0);
+        jsonWriter.value(1.0);
+        jsonWriter.value(Double.MAX_VALUE);
+        jsonWriter.value(Double.MIN_VALUE);
+        jsonWriter.value(0.0);
+        jsonWriter.value(-0.5);
+        jsonWriter.value(Double.MIN_NORMAL);
+        jsonWriter.value(Math.PI);
+        jsonWriter.value(Math.E);
+        jsonWriter.endArray();
+        jsonWriter.close();
+        assertEquals("[-0.0,"
+                + "1.0,"
+                + "1.7976931348623157E308,"
+                + "4.9E-324,"
+                + "0.0,"
+                + "-0.5,"
+                + "2.2250738585072014E-308,"
+                + "3.141592653589793,"
+                + "2.718281828459045]", stringWriter.toString());
+    }
+
+    public void testLongs() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        jsonWriter.value(0);
+        jsonWriter.value(1);
+        jsonWriter.value(-1);
+        jsonWriter.value(Long.MIN_VALUE);
+        jsonWriter.value(Long.MAX_VALUE);
+        jsonWriter.endArray();
+        jsonWriter.close();
+        assertEquals("[0,"
+                + "1,"
+                + "-1,"
+                + "-9223372036854775808,"
+                + "9223372036854775807]", stringWriter.toString());
+    }
+
+    public void testBooleans() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        jsonWriter.value(true);
+        jsonWriter.value(false);
+        jsonWriter.endArray();
+        assertEquals("[true,false]", stringWriter.toString());
+    }
+
+    public void testNulls() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        jsonWriter.nullValue();
+        jsonWriter.endArray();
+        assertEquals("[null]", stringWriter.toString());
+    }
+
+    public void testStrings() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        jsonWriter.value("a");
+        jsonWriter.value("a\"");
+        jsonWriter.value("\"");
+        jsonWriter.value(":");
+        jsonWriter.value(",");
+        jsonWriter.value("\b");
+        jsonWriter.value("\f");
+        jsonWriter.value("\n");
+        jsonWriter.value("\r");
+        jsonWriter.value("\t");
+        jsonWriter.value(" ");
+        jsonWriter.value("\\");
+        jsonWriter.value("{");
+        jsonWriter.value("}");
+        jsonWriter.value("[");
+        jsonWriter.value("]");
+        jsonWriter.value("\0");
+        jsonWriter.value("\u0019");
+        jsonWriter.endArray();
+        assertEquals("[\"a\","
+                + "\"a\\\"\","
+                + "\"\\\"\","
+                + "\":\","
+                + "\",\","
+                + "\"\\b\","
+                + "\"\\f\","
+                + "\"\\n\","
+                + "\"\\r\","
+                + "\"\\t\","
+                + "\" \","
+                + "\"\\\\\","
+                + "\"{\","
+                + "\"}\","
+                + "\"[\","
+                + "\"]\","
+                + "\"\\u0000\","
+                + "\"\\u0019\"]", stringWriter.toString());
+    }
+
+    public void testEmptyArray() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        jsonWriter.endArray();
+        assertEquals("[]", stringWriter.toString());
+    }
+
+    public void testEmptyObject() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginObject();
+        jsonWriter.endObject();
+        assertEquals("{}", stringWriter.toString());
+    }
+
+    public void testObjectsInArrays() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        jsonWriter.beginObject();
+        jsonWriter.name("a").value(5);
+        jsonWriter.name("b").value(false);
+        jsonWriter.endObject();
+        jsonWriter.beginObject();
+        jsonWriter.name("c").value(6);
+        jsonWriter.name("d").value(true);
+        jsonWriter.endObject();
+        jsonWriter.endArray();
+        assertEquals("[{\"a\":5,\"b\":false},"
+                + "{\"c\":6,\"d\":true}]", stringWriter.toString());
+    }
+
+    public void testArraysInObjects() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginObject();
+        jsonWriter.name("a");
+        jsonWriter.beginArray();
+        jsonWriter.value(5);
+        jsonWriter.value(false);
+        jsonWriter.endArray();
+        jsonWriter.name("b");
+        jsonWriter.beginArray();
+        jsonWriter.value(6);
+        jsonWriter.value(true);
+        jsonWriter.endArray();
+        jsonWriter.endObject();
+        assertEquals("{\"a\":[5,false],"
+                + "\"b\":[6,true]}", stringWriter.toString());
+    }
+
+    public void testDeepNestingArrays() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        for (int i = 0; i < 20; i++) {
+            jsonWriter.beginArray();
+        }
+        for (int i = 0; i < 20; i++) {
+            jsonWriter.endArray();
+        }
+        assertEquals("[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]", stringWriter.toString());
+    }
+
+    public void testDeepNestingObjects() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginObject();
+        for (int i = 0; i < 20; i++) {
+            jsonWriter.name("a");
+            jsonWriter.beginObject();
+        }
+        for (int i = 0; i < 20; i++) {
+            jsonWriter.endObject();
+        }
+        jsonWriter.endObject();
+        assertEquals("{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":"
+                + "{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{"
+                + "}}}}}}}}}}}}}}}}}}}}}", stringWriter.toString());
+    }
+
+    public void testRepeatedName() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginObject();
+        jsonWriter.name("a").value(true);
+        jsonWriter.name("a").value(false);
+        jsonWriter.endObject();
+        // JsonWriter doesn't attempt to detect duplicate names
+        assertEquals("{\"a\":true,\"a\":false}", stringWriter.toString());
+    }
+
+    public void testPrettyPrintObject() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.setIndent("   ");
+
+        jsonWriter.beginObject();
+        jsonWriter.name("a").value(true);
+        jsonWriter.name("b").value(false);
+        jsonWriter.name("c").value(5.0);
+        jsonWriter.name("e").nullValue();
+        jsonWriter.name("f").beginArray();
+        jsonWriter.value(6.0);
+        jsonWriter.value(7.0);
+        jsonWriter.endArray();
+        jsonWriter.name("g").beginObject();
+        jsonWriter.name("h").value(8.0);
+        jsonWriter.name("i").value(9.0);
+        jsonWriter.endObject();
+        jsonWriter.endObject();
+
+        String expected = "{\n"
+                + "   \"a\": true,\n"
+                + "   \"b\": false,\n"
+                + "   \"c\": 5.0,\n"
+                + "   \"e\": null,\n"
+                + "   \"f\": [\n"
+                + "      6.0,\n"
+                + "      7.0\n"
+                + "   ],\n"
+                + "   \"g\": {\n"
+                + "      \"h\": 8.0,\n"
+                + "      \"i\": 9.0\n"
+                + "   }\n"
+                + "}";
+        assertEquals(expected, stringWriter.toString());
+    }
+
+    public void testPrettyPrintArray() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.setIndent("   ");
+
+        jsonWriter.beginArray();
+        jsonWriter.value(true);
+        jsonWriter.value(false);
+        jsonWriter.value(5.0);
+        jsonWriter.nullValue();
+        jsonWriter.beginObject();
+        jsonWriter.name("a").value(6.0);
+        jsonWriter.name("b").value(7.0);
+        jsonWriter.endObject();
+        jsonWriter.beginArray();
+        jsonWriter.value(8.0);
+        jsonWriter.value(9.0);
+        jsonWriter.endArray();
+        jsonWriter.endArray();
+
+        String expected = "[\n"
+                + "   true,\n"
+                + "   false,\n"
+                + "   5.0,\n"
+                + "   null,\n"
+                + "   {\n"
+                + "      \"a\": 6.0,\n"
+                + "      \"b\": 7.0\n"
+                + "   },\n"
+                + "   [\n"
+                + "      8.0,\n"
+                + "      9.0\n"
+                + "   ]\n"
+                + "]";
+        assertEquals(expected, stringWriter.toString());
+    }
+}
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 77a1930..36a8e57 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -42,7 +42,6 @@
         for both to be null.
     */
     private Bitmap  mBitmap;    // if not null, mGL must be null
-    private GL      mGL;        // if not null, mBitmap must be null
     
     // optional field set by the caller
     private DrawFilter mDrawFilter;
@@ -106,31 +105,22 @@
         mDensity = bitmap.mDensity;
     }
     
-    /*package*/ Canvas(int nativeCanvas) {
+    Canvas(int nativeCanvas) {
         if (nativeCanvas == 0) {
             throw new IllegalStateException();
         }
         mNativeCanvas = nativeCanvas;
         mDensity = Bitmap.getDefaultDensity();
     }
-    
+
     /**
-     * Construct a canvas with the specified gl context. All drawing through
-     * this canvas will be redirected to OpenGL. Note: some features may not
-     * be supported in this mode (e.g. some GL implementations may not support
-     * antialiasing or certain effects like ColorMatrix or certain Xfermodes).
-     * However, no exception will be thrown in those cases.
+     * Returns null.
      * 
-     * <p>The initial target density of the canvas is the same as the initial
-     * density of bitmaps as per {@link Bitmap#getDensity() Bitmap.getDensity()}.
-     * 
-     * @deprecated This constructor is not supported and should not be invoked.
+     * @deprecated This method is not supported and should not be invoked.
      */
     @Deprecated
-    public Canvas(GL gl) {
-        mNativeCanvas = initGL();
-        mGL = gl;
-        mDensity = Bitmap.getDefaultDensity();
+    protected GL getGL() {
+        return null;
     }
 
     /**
@@ -143,32 +133,9 @@
      *         false otherwise.
      */
     public boolean isHardwareAccelerated() {
-        return mGL != null;
+        return false;
     }
-    
-    /**
-     * Return the GL object associated with this canvas, or null if it is not
-     * backed by GL.
-     * 
-     * @deprecated This method is not supported and should not be invoked.
-     */
-    @Deprecated
-    public GL getGL() {
-        return mGL;
-    }
-    
-    /**
-     * Call this to free up OpenGL resources that may be cached or allocated
-     * on behalf of the Canvas. Any subsequent drawing with a GL-backed Canvas
-     * will have to recreate those resources.
-     * 
-     * @deprecated This method is not supported and should not be invoked.
-     */
-    @Deprecated
-    public static void freeGlCaches() {
-        freeCaches();
-    }
-        
+
     /**
      * Specify a bitmap for the canvas to draw into.  As a side-effect, also
      * updates the canvas's target density to match that of the bitmap.
@@ -182,7 +149,7 @@
         if (!bitmap.isMutable()) {
             throw new IllegalStateException();
         }
-        if (mGL != null) {
+        if (isHardwareAccelerated()) {
             throw new RuntimeException("Can't set a bitmap device on a GL canvas");
         }
         throwIfRecycled(bitmap);
@@ -196,16 +163,12 @@
      * Set the viewport dimensions if this canvas is GL based. If it is not,
      * this method is ignored and no exception is thrown.
      *
-     *  @param width The width of the viewport
-     *  @param height The height of the viewport
+     * @param width The width of the viewport
+     * @param height The height of the viewport
      * 
-     * @deprecated This method is not supported and should not be invoked.
+     * @hide
      */
-    @Deprecated
     public void setViewport(int width, int height) {
-        if (mGL != null) {
-            nativeSetViewport(mNativeCanvas, width, height);
-        }
     }
 
     /**
@@ -1591,26 +1554,26 @@
     
     @Override
     protected void finalize() throws Throwable {
-        super.finalize();
-        // If the constructor threw an exception before setting mNativeCanvas, the native finalizer
-        // must not be invoked.
-        if (mNativeCanvas != 0) {
-            finalizer(mNativeCanvas);
+        try {
+            super.finalize();
+        } finally {
+            // If the constructor threw an exception before setting mNativeCanvas,
+            // the native finalizer must not be invoked.
+            if (mNativeCanvas != 0) {
+                finalizer(mNativeCanvas);
+            }
         }
     }
 
     /**
-     * Free up as much memory as possible from private caches (e.g. fonts,
-     * images)
+     * Free up as much memory as possible from private caches (e.g. fonts, images)
      *
-     * @hide - for now
+     * @hide
      */
     public static native void freeCaches();
 
     private static native int initRaster(int nativeBitmapOrZero);
-    private static native int initGL();
     private static native void native_setBitmap(int nativeCanvas, int bitmap);
-    private static native void nativeSetViewport(int nCanvas, int w, int h);
     private static native int native_saveLayer(int nativeCanvas, RectF bounds,
                                                int paint, int layerFlags);
     private static native int native_saveLayer(int nativeCanvas, float l,
diff --git a/graphics/java/android/graphics/ComposeShader.java b/graphics/java/android/graphics/ComposeShader.java
index 9b57ea4..ac1f277 100644
--- a/graphics/java/android/graphics/ComposeShader.java
+++ b/graphics/java/android/graphics/ComposeShader.java
@@ -20,6 +20,9 @@
     an {@link android.graphics.Xfermode} subclass.
 */
 public class ComposeShader extends Shader {
+    private final Shader mShaderA;
+    private final Shader mShaderB;
+
     /** Create a new compose shader, given shaders A, B, and a combining mode.
         When the mode is applied, it will be given the result from shader A as its
         "dst", and the result of from shader B as its "src".
@@ -29,6 +32,8 @@
                         is null, then SRC_OVER is assumed.
     */
     public ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode) {
+        mShaderA = shaderA;
+        mShaderB = shaderB;
         native_instance = nativeCreate1(shaderA.native_instance, shaderB.native_instance,
                 (mode != null) ? mode.native_instance : 0);
         native_shader = nativePostCreate1(native_instance, shaderA.native_shader,
@@ -43,6 +48,8 @@
         @param mode     The PorterDuff mode that combines the colors from the two shaders.
     */
     public ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode) {
+        mShaderA = shaderA;
+        mShaderB = shaderB;
         native_instance = nativeCreate2(shaderA.native_instance, shaderB.native_instance,
                 mode.nativeInt);
         native_shader = nativePostCreate2(native_instance, shaderA.native_shader,
diff --git a/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java b/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
index f4cf15c..b469d2a 100644
--- a/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
+++ b/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
@@ -57,13 +57,11 @@
      */
     public RoundRectShape(float[] outerRadii, RectF inset,
                           float[] innerRadii) {
-        if (outerRadii.length < 8) {
-            throw new ArrayIndexOutOfBoundsException(
-                                        "outer radii must have >= 8 values");
+        if (outerRadii != null && outerRadii.length < 8) {
+            throw new ArrayIndexOutOfBoundsException("outer radii must have >= 8 values");
         }
         if (innerRadii != null && innerRadii.length < 8) {
-            throw new ArrayIndexOutOfBoundsException(
-                                        "inner radii must have >= 8 values");
+            throw new ArrayIndexOutOfBoundsException("inner radii must have >= 8 values");
         }
         mOuterRadii = outerRadii;
         mInset = inset;
@@ -97,8 +95,7 @@
                            r.right - mInset.right, r.bottom - mInset.bottom);
             if (mInnerRect.width() < w && mInnerRect.height() < h) {
                 if (mInnerRadii != null) {
-                    mPath.addRoundRect(mInnerRect, mInnerRadii,
-                                       Path.Direction.CCW);
+                    mPath.addRoundRect(mInnerRect, mInnerRadii, Path.Direction.CCW);
                 } else {
                     mPath.addRect(mInnerRect, Path.Direction.CCW);
                 }
@@ -109,8 +106,8 @@
     @Override
     public RoundRectShape clone() throws CloneNotSupportedException {
         RoundRectShape shape = (RoundRectShape) super.clone();
-        shape.mOuterRadii = mOuterRadii.clone();
-        shape.mInnerRadii = mInnerRadii.clone();
+        shape.mOuterRadii = mOuterRadii != null ? mOuterRadii.clone() : null;
+        shape.mInnerRadii = mInnerRadii != null ? mInnerRadii.clone() : null;
         shape.mInset = new RectF(mInset);
         shape.mInnerRect = new RectF(mInnerRect);
         shape.mPath = new Path(mPath);
diff --git a/include/utils/String8.h b/include/utils/String8.h
index 0b18fe3..4e41410 100644
--- a/include/utils/String8.h
+++ b/include/utils/String8.h
@@ -374,7 +374,7 @@
 
 inline String8 String8::operator+(const String8& other) const
 {
-    String8 tmp;
+    String8 tmp(*this);
     tmp += other;
     return tmp;
 }
@@ -387,7 +387,7 @@
 
 inline String8 String8::operator+(const char* other) const
 {
-    String8 tmp;
+    String8 tmp(*this);
     tmp += other;
     return tmp;
 }
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 0444964..1efe6b5 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -1,33 +1,41 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES:= \
-	FontRenderer.cpp \
-	GradientCache.cpp \
-	LayerCache.cpp \
-	Matrix.cpp \
-	OpenGLRenderer.cpp \
-	Patch.cpp \
-	PatchCache.cpp \
-	PathCache.cpp \
-	Program.cpp \
-	ProgramCache.cpp \
-	SkiaColorFilter.cpp \
-	SkiaShader.cpp \
-	TextureCache.cpp
+# Only build libhwui when USE_OPENGL_RENDERER is
+# defined in the current device/board configuration
+ifeq ($(USE_OPENGL_RENDERER),true)
+	LOCAL_SRC_FILES:= \
+		FontRenderer.cpp \
+		GradientCache.cpp \
+		LayerCache.cpp \
+		Matrix.cpp \
+		OpenGLRenderer.cpp \
+		Patch.cpp \
+		PatchCache.cpp \
+		PathCache.cpp \
+		Program.cpp \
+		ProgramCache.cpp \
+		SkiaColorFilter.cpp \
+		SkiaShader.cpp \
+		TextureCache.cpp
+	
+	LOCAL_C_INCLUDES += \
+		$(JNI_H_INCLUDE) \
+		$(LOCAL_PATH)/../../include/utils \
+		external/skia/include/core \
+		external/skia/include/effects \
+		external/skia/include/images \
+		external/skia/src/ports \
+		external/skia/include/utils
 
-LOCAL_C_INCLUDES += \
-	$(JNI_H_INCLUDE) \
-	$(LOCAL_PATH)/../../include/utils \
-	external/skia/include/core \
-	external/skia/include/effects \
-	external/skia/include/images \
-	external/skia/src/ports \
-	external/skia/include/utils
+	LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER
+	LOCAL_MODULE_CLASS := SHARED_LIBRARIES
+	LOCAL_SHARED_LIBRARIES := libcutils libutils libGLESv2 libskia
+	LOCAL_MODULE := libhwui
+	LOCAL_MODULE_TAGS := optional
+	LOCAL_PRELINK_MODULE := false
+	
+	include $(BUILD_SHARED_LIBRARY)
 
-LOCAL_MODULE_CLASS := SHARED_LIBRARIES
-LOCAL_SHARED_LIBRARIES := libcutils libutils libGLESv2 libskia
-LOCAL_MODULE := libhwui
-LOCAL_PRELINK_MODULE := false
-
-include $(BUILD_SHARED_LIBRARY)
+    include $(call all-makefiles-under,$(LOCAL_PATH))
+endif
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index feb45ec..8d00e85 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -62,6 +62,27 @@
     }
 }
 
+void Font::measureCachedGlyph(CachedGlyphInfo *glyph, int x, int y, Rect *bounds) {
+    int nPenX = x + glyph->mBitmapLeft;
+    int nPenY = y + glyph->mBitmapTop;
+
+    int width = (int) glyph->mBitmapWidth;
+    int height = (int) glyph->mBitmapHeight;
+
+    if(bounds->bottom > nPenY) {
+        bounds->bottom = nPenY;
+    }
+    if(bounds->left > nPenX) {
+        bounds->left = nPenX;
+    }
+    if(bounds->right < nPenX + width) {
+        bounds->right = nPenX + width;
+    }
+    if(bounds->top < nPenY + height) {
+        bounds->top = nPenY + height;
+    }
+}
+
 void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y) {
     int nPenX = x + glyph->mBitmapLeft;
     int nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight;
@@ -88,22 +109,17 @@
     uint32_t endX = glyph->mStartX + glyph->mBitmapWidth;
     uint32_t endY = glyph->mStartY + glyph->mBitmapHeight;
 
-    if(nPenX < 0 || nPenY < 0) {
-        LOGE("Cannot render into a bitmap, some of the glyph is below zero");
-        return;
-    }
-
-    if(nPenX + glyph->mBitmapWidth >= bitmapW || nPenY + glyph->mBitmapHeight >= bitmapH) {
-        LOGE("Cannot render into a bitmap, dimentions too small");
-        return;
-    }
-
     uint32_t cacheWidth = mState->getCacheWidth();
     const uint8_t* cacheBuffer = mState->getTextTextureData();
 
-    uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
+    uint32_t cacheX = 0, cacheY = 0;
+    int32_t bX = 0, bY = 0;
     for (cacheX = glyph->mStartX, bX = nPenX; cacheX < endX; cacheX++, bX++) {
         for (cacheY = glyph->mStartY, bY = nPenY; cacheY < endY; cacheY++, bY++) {
+            if(bX < 0 || bY < 0 || bX >= (int32_t)bitmapW || bY >= (int32_t)bitmapH) {
+                LOGE("Skipping invalid index");
+                continue;
+            }
             uint8_t tempCol = cacheBuffer[cacheY * cacheWidth + cacheX];
             bitmap[bY * bitmapW + bX] = tempCol;
         }
@@ -127,7 +143,33 @@
 }
 
 void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
-        int numGlyphs, int x, int y, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) {
+                       int numGlyphs, int x, int y,
+                       uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) {
+    if(bitmap != NULL && bitmapW > 0 && bitmapH > 0) {
+        renderUTF(paint, text, start, len, numGlyphs, x, y, BITMAP,
+                   bitmap, bitmapW, bitmapH, NULL);
+    }
+    else {
+        renderUTF(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER,
+                   NULL, 0, 0, NULL);
+    }
+
+}
+
+void Font::measureUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+                       int numGlyphs, Rect *bounds) {
+    if(bounds == NULL) {
+        LOGE("No return rectangle provided to measure text");
+        return;
+    }
+    bounds->set(1e6, -1e6, -1e6, 1e6);
+    renderUTF(paint, text, start, len, numGlyphs, 0, 0, MEASURE, NULL, 0, 0, bounds);
+}
+
+void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+                       int numGlyphs, int x, int y, RenderMode mode,
+                       uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
+                       Rect *bounds) {
     if (numGlyphs == 0 || text == NULL || len == 0) {
         return;
     }
@@ -152,11 +194,16 @@
 
         // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
         if (cachedGlyph->mIsValid) {
-            if(bitmap != NULL) {
-                drawCachedGlyph(cachedGlyph, penX, penY, bitmap, bitmapW, bitmapH);
-            }
-            else {
+            switch(mode) {
+            case FRAMEBUFFER:
                 drawCachedGlyph(cachedGlyph, penX, penY);
+                break;
+            case BITMAP:
+                drawCachedGlyph(cachedGlyph, penX, penY, bitmap, bitmapW, bitmapH);
+                break;
+            case MEASURE:
+                measureCachedGlyph(cachedGlyph, penX, penY, bounds);
+                break;
             }
         }
 
@@ -245,6 +292,9 @@
     mCurrentQuadIndex = 0;
     mTextureId = 0;
 
+    mTextMeshPtr = NULL;
+    mTextTexture = NULL;
+
     mIndexBufferID = 0;
 
     mCacheWidth = DEFAULT_TEXT_CACHE_WIDTH;
@@ -272,10 +322,12 @@
     }
     mCacheLines.clear();
 
-    delete[] mTextMeshPtr;
-    delete[] mTextTexture;
+    if (mInitialized) {
+        delete[] mTextMeshPtr;
+        delete[] mTextTexture;
+    }
 
-    if(mTextureId) {
+    if (mTextureId) {
         glDeleteTextures(1, &mTextureId);
     }
 
@@ -569,6 +621,32 @@
         precacheLatin(paint);
     }
 }
+FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text,
+                                uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius) {
+
+    Rect bounds;
+    mCurrentFont->measureUTF(paint, text, startIndex, len, numGlyphs, &bounds);
+    uint32_t paddedWidth = (uint32_t)(bounds.right - bounds.left) + 2*radius;
+    uint32_t paddedHeight = (uint32_t)(bounds.top - bounds.bottom) + 2*radius;
+    uint8_t* dataBuffer = new uint8_t[paddedWidth * paddedHeight];
+    for(uint32_t i = 0; i < paddedWidth * paddedHeight; i ++) {
+        dataBuffer[i] = 0;
+    }
+    int penX = radius - bounds.left;
+    int penY = radius - bounds.bottom;
+
+    mCurrentFont->renderUTF(paint, text, startIndex, len, numGlyphs, penX, penY,
+                              dataBuffer, paddedWidth, paddedHeight);
+    blurImage(dataBuffer, paddedWidth, paddedHeight, radius);
+
+    DropShadow image;
+    image.width = paddedWidth;
+    image.height = paddedHeight;
+    image.image = dataBuffer;
+    image.penX = penX;
+    image.penY = penY;
+    return image;
+}
 
 void FontRenderer::renderText(SkPaint* paint, const Rect* clip, const char *text,
         uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y) {
@@ -598,11 +676,11 @@
     // and sigma varies with radius.
     // Based on some experimental radius values and sigma's
     // we approximately fit sigma = f(radius) as
-    // sigma = radius * 0.4  + 0.6
+    // sigma = radius * 0.3  + 0.6
     // The larger the radius gets, the more our gaussian blur
     // will resemble a box blur since with large sigma
     // the gaussian curve begins to lose its shape
-    float sigma = 0.4f * (float)radius + 0.6f;
+    float sigma = 0.3f * (float)radius + 0.6f;
 
     // Now compute the coefficints
     // We will store some redundant values to save some math during
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index 4cd902e..6346ded 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -45,6 +45,7 @@
 
     /**
      * Renders the specified string of text.
+     * If bitmap is specified, it will be used as the render target
      */
     void renderUTF(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
                      int numGlyphs, int x, int y,
@@ -57,6 +58,20 @@
 protected:
     friend class FontRenderer;
 
+    enum RenderMode {
+        FRAMEBUFFER,
+        BITMAP,
+        MEASURE,
+    };
+
+    void renderUTF(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+                     int numGlyphs, int x, int y, RenderMode mode,
+                     uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
+                     Rect *bounds);
+
+    void measureUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+                      int numGlyphs, Rect *bounds);
+
     struct CachedGlyphInfo {
         // Has the cache been invalidated?
         bool mIsValid;
@@ -89,6 +104,7 @@
 
     CachedGlyphInfo* cacheGlyph(SkPaint* paint, int32_t glyph);
     void updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo *glyph);
+    void measureCachedGlyph(CachedGlyphInfo *glyph, int x, int y, Rect *bounds);
     void drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y);
     void drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y,
                           uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH);
@@ -112,6 +128,19 @@
     void renderText(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex,
             uint32_t len, int numGlyphs, int x, int y);
 
+    struct DropShadow {
+        uint32_t width;
+        uint32_t height;
+        uint8_t* image;
+        int32_t penX;
+        int32_t penY;
+    };
+
+    // After renderDropShadow returns, the called owns the memory in DropShadow.image
+    // and is responsible for releasing it when it's done with it
+    DropShadow renderDropShadow(SkPaint* paint, const char *text, uint32_t startIndex,
+                                   uint32_t len, int numGlyphs, uint32_t radius);
+
     GLuint getTexture() {
         checkInit();
         return mTextureId;
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
index aeda416..59fa0a7 100644
--- a/libs/hwui/GradientCache.cpp
+++ b/libs/hwui/GradientCache.cpp
@@ -80,8 +80,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 Texture* GradientCache::get(SkShader* shader) {
-    Texture* texture = mCache.get(shader);
-    return texture;
+    return mCache.get(shader);
 }
 
 void GradientCache::remove(SkShader* shader) {
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index a72045b..5d30b1a 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -436,6 +436,9 @@
     }
 
     const Texture* texture = mTextureCache.get(bitmap);
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
+
     drawTextureRect(left, top, right, bottom, texture, paint);
 }
 
@@ -449,6 +452,9 @@
     }
 
     const Texture* texture = mTextureCache.get(bitmap);
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
+
     drawTextureRect(r.left, r.top, r.right, r.bottom, texture, paint);
 }
 
@@ -461,6 +467,8 @@
     }
 
     const Texture* texture = mTextureCache.get(bitmap);
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
 
     const float width = texture->width;
     const float height = texture->height;
@@ -484,6 +492,8 @@
     }
 
     const Texture* texture = mTextureCache.get(bitmap);
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
 
     int alpha;
     SkXfermode::Mode mode;
@@ -610,7 +620,9 @@
     GLuint textureUnit = 0;
     glActiveTexture(gTextureUnits[textureUnit]);
 
-    PathTexture* texture = mPathCache.get(path, paint);
+    const PathTexture* texture = mPathCache.get(path, paint);
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
 
     int alpha;
     SkXfermode::Mode mode;
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 67a5f92..4a01ffa 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -34,6 +34,10 @@
         mCache(GenerationCache<PathCacheEntry, PathTexture*>::kUnlimitedCapacity),
         mSize(0), mMaxSize(maxByteSize) {
     mCache.setOnEntryRemovedListener(this);
+
+    GLint maxTextureSize;
+    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
+    mMaxTextureSize = maxTextureSize;
 }
 
 PathCache::~PathCache() {
@@ -88,16 +92,24 @@
         texture = addTexture(entry, path, paint);
     }
 
-    // TODO: Do something to destroy the texture object if it's too big for the cache
     return texture;
 }
 
 PathTexture* PathCache::addTexture(const PathCacheEntry& entry,
         const SkPath *path, const SkPaint* paint) {
     const SkRect& bounds = path->getBounds();
+
+    const float pathWidth = bounds.width();
+    const float pathHeight = bounds.height();
+
+    if (pathWidth > mMaxTextureSize || pathHeight > mMaxTextureSize) {
+        LOGW("Path too large to be rendered into a texture");
+        return NULL;
+    }
+
     const float offset = entry.strokeWidth * 1.5f;
-    const uint32_t width = uint32_t(bounds.width() + offset * 2.0 + 0.5);
-    const uint32_t height = uint32_t(bounds.height() + offset * 2.0 + 0.5);
+    const uint32_t width = uint32_t(pathWidth + offset * 2.0 + 0.5);
+    const uint32_t height = uint32_t(pathHeight + offset * 2.0 + 0.5);
 
     const uint32_t size = width * height;
     // Don't even try to cache a bitmap that's bigger than the cache
@@ -129,6 +141,8 @@
     if (size < mMaxSize) {
         mSize += size;
         mCache.put(entry, texture);
+    } else {
+        texture->cleanup = true;
     }
 
     return texture;
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
index 87a9141..d09789f 100644
--- a/libs/hwui/PathCache.h
+++ b/libs/hwui/PathCache.h
@@ -71,6 +71,9 @@
  * Alpha texture used to represent a path.
  */
 struct PathTexture: public Texture {
+    PathTexture(): Texture() {
+    }
+
     /**
      * Left coordinate of the path bounds.
      */
@@ -136,6 +139,7 @@
 
     uint32_t mSize;
     uint32_t mMaxSize;
+    GLuint mMaxTextureSize;
 }; // class PathCache
 
 }; // namespace uirenderer
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index fe4b54d..42c0621 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -75,11 +75,13 @@
 
 SkiaBitmapShader::SkiaBitmapShader(SkBitmap* bitmap, SkShader* key, SkShader::TileMode tileX,
         SkShader::TileMode tileY, SkMatrix* matrix, bool blend):
-        SkiaShader(kBitmap, key, tileX, tileY, matrix, blend), mBitmap(bitmap) {
+        SkiaShader(kBitmap, key, tileX, tileY, matrix, blend), mBitmap(bitmap), mTexture(NULL) {
 }
 
 void SkiaBitmapShader::describe(ProgramDescription& description, const Extensions& extensions) {
     const Texture* texture = mTextureCache->get(mBitmap);
+    if (!texture) return;
+    mTexture = texture;
 
     const float width = texture->width;
     const float height = texture->height;
@@ -98,7 +100,11 @@
         const Snapshot& snapshot, GLuint* textureUnit) {
     GLuint textureSlot = (*textureUnit)++;
     glActiveTexture(gTextureUnitsMap[textureSlot]);
-    const Texture* texture = mTextureCache->get(mBitmap);
+
+    const Texture* texture = mTexture;
+    mTexture = NULL;
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
 
     const float width = texture->width;
     const float height = texture->height;
@@ -133,9 +139,9 @@
 }
 
 SkiaLinearGradientShader::~SkiaLinearGradientShader() {
-    delete mBounds;
-    delete mColors;
-    delete mPositions;
+    delete[] mBounds;
+    delete[] mColors;
+    delete[] mPositions;
 }
 
 void SkiaLinearGradientShader::describe(ProgramDescription& description,
diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h
index 58f2870..d95e3b0 100644
--- a/libs/hwui/SkiaShader.h
+++ b/libs/hwui/SkiaShader.h
@@ -116,6 +116,7 @@
     }
 
     SkBitmap* mBitmap;
+    const Texture* mTexture;
 }; // struct SkiaBitmapShader
 
 /**
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index d37013d..90f548b 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -26,6 +26,10 @@
  * Represents an OpenGL texture.
  */
 struct Texture {
+    Texture() {
+        cleanup = false;
+    }
+
     /**
      * Name of the texture.
      */
@@ -46,8 +50,26 @@
      * Height of the backing bitmap.
      */
     uint32_t height;
+    /**
+     * Indicates whether this texture should be cleaned up after use.
+     */
+    bool cleanup;
 }; // struct Texture
 
+class AutoTexture {
+public:
+    AutoTexture(const Texture* texture): mTexture(texture) { }
+    ~AutoTexture() {
+        if (mTexture && mTexture->cleanup) {
+            glDeleteTextures(1, &mTexture->id);
+            delete mTexture;
+        }
+    }
+
+private:
+    const Texture* mTexture;
+}; // class AutoTexture
+
 }; // namespace uirenderer
 }; // namespace android
 
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index 4975edb..3f9698d 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -31,6 +31,9 @@
         mCache(GenerationCache<SkBitmap*, Texture*>::kUnlimitedCapacity),
         mSize(0), mMaxSize(maxByteSize) {
     mCache.setOnEntryRemovedListener(this);
+
+    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
+    LOGD("Maximum texture dimension is %d pixels", mMaxTextureSize);
 }
 
 TextureCache::~TextureCache() {
@@ -79,6 +82,11 @@
 Texture* TextureCache::get(SkBitmap* bitmap) {
     Texture* texture = mCache.get(bitmap);
     if (!texture) {
+        if (bitmap->width() > mMaxTextureSize || bitmap->height() > mMaxTextureSize) {
+            LOGW("Bitmap too large to be uploaded into a texture");
+            return NULL;
+        }
+
         const uint32_t size = bitmap->rowBytes() * bitmap->height();
         // Don't even try to cache a bitmap that's bigger than the cache
         if (size < mMaxSize) {
@@ -93,11 +101,13 @@
         if (size < mMaxSize) {
             mSize += size;
             mCache.put(bitmap, texture);
+        } else {
+            texture->cleanup = true;
         }
     } else if (bitmap->getGenerationID() != texture->generation) {
         generateTexture(bitmap, texture, true);
     }
-    // TODO: Do something to destroy the texture object if it's too big for the cache
+
     return texture;
 }
 
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index bed1191..452716c 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -82,6 +82,7 @@
 
     uint32_t mSize;
     uint32_t mMaxSize;
+    GLint mMaxTextureSize;
 }; // class TextureCache
 
 }; // namespace uirenderer
diff --git a/libs/rs/rsScriptC_Lib.cpp b/libs/rs/rsScriptC_Lib.cpp
index dc30afc..ac32810 100644
--- a/libs/rs/rsScriptC_Lib.cpp
+++ b/libs/rs/rsScriptC_Lib.cpp
@@ -267,18 +267,31 @@
 static void SC_debugF(const char *s, float f) {
     LOGE("%s %f, 0x%08x", s, f, *((int *) (&f)));
 }
-static void SC_debugFv2(const char *s, rsvF_2 fv) {
-    float *f = (float *)&fv;
-    LOGE("%s {%f, %f}", s, f[0], f[1]);
+static void SC_debugFv2(const char *s, float f1, float f2) {
+    LOGE("%s {%f, %f}", s, f1, f2);
 }
-static void SC_debugFv3(const char *s, rsvF_4 fv) {
-    float *f = (float *)&fv;
-    LOGE("%s {%f, %f, %f}", s, f[0], f[1], f[2]);
+static void SC_debugFv3(const char *s, float f1, float f2, float f3) {
+    LOGE("%s {%f, %f, %f}", s, f1, f2, f3);
 }
-static void SC_debugFv4(const char *s, rsvF_4 fv) {
-    float *f = (float *)&fv;
-    LOGE("%s {%f, %f, %f, %f}", s, f[0], f[1], f[2], f[3]);
+static void SC_debugFv4(const char *s, float f1, float f2, float f3, float f4) {
+    LOGE("%s {%f, %f, %f, %f}", s, f1, f2, f3, f4);
 }
+static void SC_debugFM4v4(const char *s, const float *f) {
+    LOGE("%s {%f, %f, %f, %f", s, f[0], f[4], f[8], f[12]);
+    LOGE("%s  %f, %f, %f, %f", s, f[1], f[5], f[9], f[13]);
+    LOGE("%s  %f, %f, %f, %f", s, f[2], f[6], f[10], f[14]);
+    LOGE("%s  %f, %f, %f, %f}", s, f[3], f[7], f[11], f[15]);
+}
+static void SC_debugFM3v3(const char *s, const float *f) {
+    LOGE("%s {%f, %f, %f", s, f[0], f[3], f[6]);
+    LOGE("%s  %f, %f, %f", s, f[1], f[4], f[7]);
+    LOGE("%s  %f, %f, %f}",s, f[2], f[5], f[8]);
+}
+static void SC_debugFM2v2(const char *s, const float *f) {
+    LOGE("%s {%f, %f", s, f[0], f[2]);
+    LOGE("%s  %f, %f}",s, f[1], f[3]);
+}
+
 static void SC_debugI32(const char *s, int32_t i) {
     LOGE("%s %i  0x%x", s, i, i);
 }
@@ -394,9 +407,12 @@
 
     // Debug
     { "_Z7rsDebugPKcf", (void *)&SC_debugF },
-    { "_Z7rsDebugPKcDv2_f", (void *)&SC_debugFv2 },
-    { "_Z7rsDebugPKcDv3_f", (void *)&SC_debugFv3 },
-    { "_Z7rsDebugPKcDv4_f", (void *)&SC_debugFv4 },
+    { "_Z7rsDebugPKcff", (void *)&SC_debugFv2 },
+    { "_Z7rsDebugPKcfff", (void *)&SC_debugFv3 },
+    { "_Z7rsDebugPKcffff", (void *)&SC_debugFv4 },
+    { "_Z7rsDebugPKcPK12rs_matrix4x4", (void *)&SC_debugFM4v4 },
+    { "_Z7rsDebugPKcPK12rs_matrix3x3", (void *)&SC_debugFM3v3 },
+    { "_Z7rsDebugPKcPK12rs_matrix2x2", (void *)&SC_debugFM2v2 },
     { "_Z7rsDebugPKci", (void *)&SC_debugI32 },
     { "_Z7rsDebugPKcj", (void *)&SC_debugU32 },
     { "_Z7rsDebugPKcPKv", (void *)&SC_debugP },
diff --git a/libs/rs/scriptc/rs_core.rsh b/libs/rs/scriptc/rs_core.rsh
index 93e009a..85f3b25 100644
--- a/libs/rs/scriptc/rs_core.rsh
+++ b/libs/rs/scriptc/rs_core.rsh
@@ -1,6 +1,15 @@
 #ifndef __RS_CORE_RSH__
 #define __RS_CORE_RSH__
 
+static void __attribute__((overloadable)) rsDebug(const char *s, float2 v) {
+    rsDebug(s, v.x, v.y);
+}
+static void __attribute__((overloadable)) rsDebug(const char *s, float3 v) {
+    rsDebug(s, v.x, v.y, v.z);
+}
+static void __attribute__((overloadable)) rsDebug(const char *s, float4 v) {
+    rsDebug(s, v.x, v.y, v.z, v.w);
+}
 
 static uchar4 __attribute__((overloadable)) rsPackColorTo8888(float r, float g, float b)
 {
diff --git a/libs/rs/scriptc/rs_math.rsh b/libs/rs/scriptc/rs_math.rsh
index 66171d8..45f6bf4 100644
--- a/libs/rs/scriptc/rs_math.rsh
+++ b/libs/rs/scriptc/rs_math.rsh
@@ -1,6 +1,31 @@
 #ifndef __RS_MATH_RSH__
 #define __RS_MATH_RSH__
 
+// Debugging, print to the LOG a description string and a value.
+extern void __attribute__((overloadable))
+    rsDebug(const char *, float);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, float, float);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, float, float, float);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, float, float, float, float);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, const rs_matrix4x4 *);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, const rs_matrix3x3 *);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, const rs_matrix2x2 *);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, int);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, uint);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, const void *);
+#define RS_DEBUG(a) rsDebug(#a, a)
+#define RS_DEBUG_MARKER rsDebug(__FILE__, __LINE__)
+
+
 #include "rs_cl.rsh"
 #include "rs_core.rsh"
 
@@ -31,25 +56,6 @@
 extern const void * __attribute__((overloadable))
     rsGetElementAt(rs_allocation, uint32_t x, uint32_t y, uint32_t z);
 
-
-// Debugging, print to the LOG a description string and a value.
-extern void __attribute__((overloadable))
-    rsDebug(const char *, float);
-extern void __attribute__((overloadable))
-    rsDebug(const char *, float2);
-extern void __attribute__((overloadable))
-    rsDebug(const char *, float3);
-extern void __attribute__((overloadable))
-    rsDebug(const char *, float4);
-extern void __attribute__((overloadable))
-    rsDebug(const char *, int);
-extern void __attribute__((overloadable))
-    rsDebug(const char *, uint);
-extern void __attribute__((overloadable))
-    rsDebug(const char *, const void *);
-#define RS_DEBUG(a) rsDebug(#a, a)
-#define RS_DEBUG_MARKER rsDebug(__FILE__, __LINE__)
-
 // Return a random value between 0 (or min_value) and max_malue.
 extern int __attribute__((overloadable))
     rsRand(int max_value);
diff --git a/libs/utils/tests/Android.mk b/libs/utils/tests/Android.mk
index b9f206a..725de9c 100644
--- a/libs/utils/tests/Android.mk
+++ b/libs/utils/tests/Android.mk
@@ -7,7 +7,8 @@
 # Build the unit tests.
 test_src_files := \
 	ObbFile_test.cpp \
-	PollLoop_test.cpp
+	PollLoop_test.cpp \
+	String8_test.cpp
 
 shared_libraries := \
 	libz \
@@ -41,4 +42,4 @@
     $(eval include $(BUILD_EXECUTABLE)) \
 )
 
-endif
\ No newline at end of file
+endif
diff --git a/libs/utils/tests/String8_test.cpp b/libs/utils/tests/String8_test.cpp
new file mode 100644
index 0000000..c42c68d
--- /dev/null
+++ b/libs/utils/tests/String8_test.cpp
@@ -0,0 +1,75 @@
+/*
+ * 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_TAG "String8_test"
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+#include <gtest/gtest.h>
+
+namespace android {
+
+class String8Test : public testing::Test {
+protected:
+    virtual void SetUp() {
+    }
+
+    virtual void TearDown() {
+    }
+};
+
+TEST_F(String8Test, Cstr) {
+    String8 tmp("Hello, world!");
+
+    EXPECT_STREQ(tmp.string(), "Hello, world!");
+}
+
+TEST_F(String8Test, OperatorPlus) {
+    String8 src1("Hello, ");
+
+    // Test adding String8 + const char*
+    const char* ccsrc2 = "world!";
+    String8 dst1 = src1 + ccsrc2;
+    EXPECT_STREQ(dst1.string(), "Hello, world!");
+    EXPECT_STREQ(src1.string(), "Hello, ");
+    EXPECT_STREQ(ccsrc2, "world!");
+
+    // Test adding String8 + String8
+    String8 ssrc2("world!");
+    String8 dst2 = src1 + ssrc2;
+    EXPECT_STREQ(dst2.string(), "Hello, world!");
+    EXPECT_STREQ(src1.string(), "Hello, ");
+    EXPECT_STREQ(ssrc2.string(), "world!");
+}
+
+TEST_F(String8Test, OperatorPlusEquals) {
+    String8 src1("My voice");
+
+    // Testing String8 += String8
+    String8 src2(" is my passport.");
+    src1 += src2;
+    EXPECT_STREQ(src1.string(), "My voice is my passport.");
+    EXPECT_STREQ(src2.string(), " is my passport.");
+
+    // Adding const char* to the previous string.
+    const char* src3 = " Verify me.";
+    src1 += src3;
+    EXPECT_STREQ(src1.string(), "My voice is my passport. Verify me.");
+    EXPECT_STREQ(src2.string(), " is my passport.");
+    EXPECT_STREQ(src3, " Verify me.");
+}
+
+}
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index 805f86b..5756e53 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -38,8 +38,8 @@
 #include <surfaceflinger/ISurface.h>
 #include <utils/Errors.h>
 #include <sys/types.h>
-#include <unistd.h>
 #include <ctype.h>
+#include <unistd.h>
 
 #include "ARTPWriter.h"
 
@@ -906,7 +906,7 @@
 }
 
 status_t StagefrightRecorder::setupCameraSource() {
-    if(!mCaptureTimeLapse) {
+    if (!mCaptureTimeLapse) {
         // Dont clip for time lapse capture as encoder will have enough
         // time to encode because of slow capture rate of time lapse.
         clipVideoBitRate();
@@ -929,9 +929,10 @@
     // Set the actual video recording frame size
     CameraParameters params(mCamera->getParameters());
 
-    // dont change the preview size for time lapse as mVideoWidth, mVideoHeight
-    // may correspond to HD resolution not supported by video camera.
-    if (!mCaptureTimeLapse) {
+    // dont change the preview size when using still camera for time lapse
+    // as mVideoWidth, mVideoHeight may correspond to HD resolution not
+    // supported by the video camera.
+    if (!(mCaptureTimeLapse && mUseStillCameraForTimeLapse)) {
         params.setPreviewSize(mVideoWidth, mVideoHeight);
     }
 
@@ -947,7 +948,7 @@
     // Check on video frame size
     int frameWidth = 0, frameHeight = 0;
     newCameraParams.getPreviewSize(&frameWidth, &frameHeight);
-    if (!mCaptureTimeLapse &&
+    if (!(mCaptureTimeLapse && mUseStillCameraForTimeLapse) &&
         (frameWidth  < 0 || frameWidth  != mVideoWidth ||
         frameHeight < 0 || frameHeight != mVideoHeight)) {
         LOGE("Failed to set the video frame size to %dx%d",
diff --git a/media/libstagefright/AMRExtractor.cpp b/media/libstagefright/AMRExtractor.cpp
index 9fc1d27..70af2da 100644
--- a/media/libstagefright/AMRExtractor.cpp
+++ b/media/libstagefright/AMRExtractor.cpp
@@ -263,6 +263,7 @@
 
     buffer->set_range(0, frameSize);
     buffer->meta_data()->setInt64(kKeyTime, mCurrentTimeUs);
+    buffer->meta_data()->setInt32(kKeyIsSyncFrame, 1);
 
     mOffset += frameSize;
     mCurrentTimeUs += 20000;  // Each frame is 20ms
diff --git a/media/libstagefright/MP3Extractor.cpp b/media/libstagefright/MP3Extractor.cpp
index 2248e23..4058fbc 100644
--- a/media/libstagefright/MP3Extractor.cpp
+++ b/media/libstagefright/MP3Extractor.cpp
@@ -683,6 +683,7 @@
     buffer->set_range(0, frame_size);
 
     buffer->meta_data()->setInt64(kKeyTime, mCurrentTimeUs);
+    buffer->meta_data()->setInt32(kKeyIsSyncFrame, 1);
 
     mCurrentPos += frame_size;
     mCurrentTimeUs += frame_size * 8000ll / bitrate;
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 7cea629..6af3a7f 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -492,7 +492,6 @@
         case FOURCC('m', 'o', 'o', 'f'):
         case FOURCC('t', 'r', 'a', 'f'):
         case FOURCC('m', 'f', 'r', 'a'):
-        case FOURCC('s', 'k', 'i' ,'p'):
         case FOURCC('u', 'd', 't', 'a'):
         case FOURCC('i', 'l', 's', 't'):
         {
@@ -1552,13 +1551,14 @@
     off_t offset;
     size_t size;
     uint32_t dts;
+    bool isSyncSample;
     bool newBuffer = false;
     if (mBuffer == NULL) {
         newBuffer = true;
 
         status_t err =
             mSampleTable->getMetaDataForSample(
-                    mCurrentSampleIndex, &offset, &size, &dts);
+                    mCurrentSampleIndex, &offset, &size, &dts, &isSyncSample);
 
         if (err != OK) {
             return err;
@@ -1595,6 +1595,10 @@
                         kKeyTargetTime, targetSampleTimeUs);
             }
 
+            if (isSyncSample) {
+                mBuffer->meta_data()->setInt32(kKeyIsSyncFrame, 1);
+            }
+
             ++mCurrentSampleIndex;
         }
 
@@ -1697,6 +1701,10 @@
                     kKeyTargetTime, targetSampleTimeUs);
         }
 
+        if (isSyncSample) {
+            mBuffer->meta_data()->setInt32(kKeyIsSyncFrame, 1);
+        }
+
         ++mCurrentSampleIndex;
 
         *out = mBuffer;
diff --git a/media/libstagefright/OggExtractor.cpp b/media/libstagefright/OggExtractor.cpp
index 641a876..b699d8f 100644
--- a/media/libstagefright/OggExtractor.cpp
+++ b/media/libstagefright/OggExtractor.cpp
@@ -181,6 +181,8 @@
     }
 #endif
 
+    packet->meta_data()->setInt32(kKeyIsSyncFrame, 1);
+
     *out = packet;
 
     return OK;
diff --git a/media/libstagefright/SampleTable.cpp b/media/libstagefright/SampleTable.cpp
index 2e62f9f..27faf4f 100644
--- a/media/libstagefright/SampleTable.cpp
+++ b/media/libstagefright/SampleTable.cpp
@@ -55,6 +55,8 @@
       mTimeToSample(NULL),
       mSyncSampleOffset(-1),
       mNumSyncSamples(0),
+      mSyncSamples(NULL),
+      mLastSyncSampleIndex(0),
       mSampleToChunkEntries(NULL) {
     mSampleIterator = new SampleIterator(this);
 }
@@ -63,6 +65,9 @@
     delete[] mSampleToChunkEntries;
     mSampleToChunkEntries = NULL;
 
+    delete[] mSyncSamples;
+    mSyncSamples = NULL;
+
     delete[] mTimeToSample;
     mTimeToSample = NULL;
 
@@ -278,6 +283,18 @@
     if (mNumSyncSamples < 2) {
         LOGW("Table of sync samples is empty or has only a single entry!");
     }
+
+    mSyncSamples = new uint32_t[mNumSyncSamples];
+    size_t size = mNumSyncSamples * sizeof(uint32_t);
+    if (mDataSource->readAt(mSyncSampleOffset + 8, mSyncSamples, size)
+            != (ssize_t)size) {
+        return ERROR_IO;
+    }
+
+    for (size_t i = 0; i < mNumSyncSamples; ++i) {
+        mSyncSamples[i] = ntohl(mSyncSamples[i]) - 1;
+    }
+
     return OK;
 }
 
@@ -394,14 +411,7 @@
 
     uint32_t left = 0;
     while (left < mNumSyncSamples) {
-        uint32_t x;
-        if (mDataSource->readAt(
-                    mSyncSampleOffset + 8 + left * 4, &x, 4) != 4) {
-            return ERROR_IO;
-        }
-
-        x = ntohl(x);
-        --x;
+        uint32_t x = mSyncSamples[left];
 
         if (x >= start_sample_index) {
             break;
@@ -421,14 +431,7 @@
     --x;
 
     if (left + 1 < mNumSyncSamples) {
-        uint32_t y;
-        if (mDataSource->readAt(
-                    mSyncSampleOffset + 8 + (left + 1) * 4, &y, 4) != 4) {
-            return ERROR_IO;
-        }
-
-        y = ntohl(y);
-        --y;
+        uint32_t y = mSyncSamples[left + 1];
 
         // our sample lies between sync samples x and y.
 
@@ -486,13 +489,7 @@
                     return ERROR_OUT_OF_RANGE;
                 }
 
-                if (mDataSource->readAt(
-                            mSyncSampleOffset + 8 + (left + 1) * 4, &x, 4) != 4) {
-                    return ERROR_IO;
-                }
-
-                x = ntohl(x);
-                --x;
+                x = mSyncSamples[left + 1];
 
                 CHECK(x >= start_sample_index);
             }
@@ -532,13 +529,7 @@
     }
 
     for (size_t i = 0; i < numSamplesToScan; ++i) {
-        uint32_t x;
-        if (mDataSource->readAt(
-                    mSyncSampleOffset + 8 + i * 4, &x, 4) != 4) {
-            return ERROR_IO;
-        }
-        x = ntohl(x);
-        --x;
+        uint32_t x = mSyncSamples[i];
 
         // Now x is a sample index.
         size_t sampleSize;
@@ -568,7 +559,8 @@
         uint32_t sampleIndex,
         off_t *offset,
         size_t *size,
-        uint32_t *decodingTime) {
+        uint32_t *decodingTime,
+        bool *isSyncSample) {
     Mutex::Autolock autoLock(mLock);
 
     status_t err;
@@ -588,6 +580,28 @@
         *decodingTime = mSampleIterator->getSampleTime();
     }
 
+    if (isSyncSample) {
+        *isSyncSample = false;
+        if (mSyncSampleOffset < 0) {
+            // Every sample is a sync sample.
+            *isSyncSample = true;
+        } else {
+            size_t i = (mLastSyncSampleIndex < mNumSyncSamples)
+                    && (mSyncSamples[mLastSyncSampleIndex] <= sampleIndex)
+                ? mLastSyncSampleIndex : 0;
+
+            while (i < mNumSyncSamples && mSyncSamples[i] < sampleIndex) {
+                ++i;
+            }
+
+            if (i < mNumSyncSamples && mSyncSamples[i] == sampleIndex) {
+                *isSyncSample = true;
+            }
+
+            mLastSyncSampleIndex = i;
+        }
+    }
+
     return OK;
 }
 
diff --git a/media/libstagefright/WAVExtractor.cpp b/media/libstagefright/WAVExtractor.cpp
index 7c2b07e..39b1b96 100644
--- a/media/libstagefright/WAVExtractor.cpp
+++ b/media/libstagefright/WAVExtractor.cpp
@@ -358,6 +358,7 @@
             1000000LL * (mCurrentPos - mOffset)
                 / (mNumChannels * bytesPerSample) / mSampleRate);
 
+    buffer->meta_data()->setInt32(kKeyIsSyncFrame, 1);
 
     *out = buffer;
 
diff --git a/media/libstagefright/include/SampleTable.h b/media/libstagefright/include/SampleTable.h
index a2b2c99..f830690 100644
--- a/media/libstagefright/include/SampleTable.h
+++ b/media/libstagefright/include/SampleTable.h
@@ -60,7 +60,8 @@
             uint32_t sampleIndex,
             off_t *offset,
             size_t *size,
-            uint32_t *decodingTime);
+            uint32_t *decodingTime,
+            bool *isSyncSample = NULL);
 
     enum {
         kFlagBefore,
@@ -105,6 +106,8 @@
 
     off_t mSyncSampleOffset;
     uint32_t mNumSyncSamples;
+    uint32_t *mSyncSamples;
+    size_t mLastSyncSampleIndex;
 
     SampleIterator *mSampleIterator;
 
diff --git a/media/libstagefright/matroska/MatroskaExtractor.cpp b/media/libstagefright/matroska/MatroskaExtractor.cpp
index 3739be1..71f6587 100644
--- a/media/libstagefright/matroska/MatroskaExtractor.cpp
+++ b/media/libstagefright/matroska/MatroskaExtractor.cpp
@@ -296,6 +296,7 @@
 
     MediaBuffer *buffer = new MediaBuffer(size + 2);
     buffer->meta_data()->setInt64(kKeyTime, timeUs);
+    buffer->meta_data()->setInt32(kKeyIsSyncFrame, block->IsKey());
 
     long res = block->Read(
             mExtractor->mReader, (unsigned char *)buffer->data() + 2);
diff --git a/packages/SystemUI/res/layout/status_bar_latest_event.xml b/packages/SystemUI/res/layout/status_bar_latest_event.xml
new file mode 100644
index 0000000..88d9739
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_latest_event.xml
@@ -0,0 +1,24 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="65sp"
+    android:orientation="vertical"
+    >
+
+    <com.android.systemui.statusbar.LatestItemView android:id="@+id/content"
+            android:layout_width="match_parent"
+            android:layout_height="64sp"
+            android:background="@android:drawable/status_bar_item_background"
+            android:focusable="true"
+            android:clickable="true"
+            android:paddingRight="6sp"
+            >
+    </com.android.systemui.statusbar.LatestItemView>
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1sp"
+        android:background="@android:drawable/divider_horizontal_bright"
+        />
+
+</LinearLayout>
+
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 2299852..73fa93c 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -2412,8 +2412,17 @@
                 }
             } else {
                 mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
-                if (mActionBar != null && mActionBar.getTitle() == null) {
-                    mActionBar.setWindowTitle(mTitle);
+                if (mActionBar != null) {
+                    if (mActionBar.getTitle() == null) {
+                        mActionBar.setWindowTitle(mTitle);
+                    }
+                    // Post the panel invalidate for later; avoid application onCreateOptionsMenu
+                    // being called in the middle of onCreate or similar.
+                    mDecor.post(new Runnable() {
+                        public void run() {
+                            invalidatePanelMenu(FEATURE_ACTION_BAR);
+                        }
+                    });
                 }
             }
         }
diff --git a/services/java/com/android/server/ViewServer.java b/services/java/com/android/server/ViewServer.java
index b369f71..7b5d18a 100644
--- a/services/java/com/android/server/ViewServer.java
+++ b/services/java/com/android/server/ViewServer.java
@@ -60,6 +60,8 @@
     private static final String COMMAND_WINDOW_MANAGER_LIST = "LIST";
     // Keeps a connection open and notifies when the list of windows changes
     private static final String COMMAND_WINDOW_MANAGER_AUTOLIST = "AUTOLIST";
+    // Returns the focused window
+    private static final String COMMAND_WINDOW_MANAGER_GET_FOCUS = "GET_FOCUS";
 
     private ServerSocket mServer;
     private Thread mThread;
@@ -250,6 +252,8 @@
                     result = writeValue(mClient, VALUE_SERVER_VERSION);
                 } else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) {
                     result = mWindowManager.viewServerListWindows(mClient);
+                } else if (COMMAND_WINDOW_MANAGER_GET_FOCUS.equalsIgnoreCase(command)) {
+                    result = mWindowManager.viewServerGetFocusedWindow(mClient);
                 } else if(COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) {
                     result = windowManagerAutolistLoop();
                 } else {
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index c4de958..b1e8968 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -4720,6 +4720,51 @@
     }
 
     /**
+     * Returns the focused window in the following format:
+     * windowHashCodeInHexadecimal windowName
+     *
+     * @param client The remote client to send the listing to.
+     * @return False if an error occurred, true otherwise.
+     */
+    boolean viewServerGetFocusedWindow(Socket client) {
+        if (isSystemSecure()) {
+            return false;
+        }
+
+        boolean result = true;
+
+        WindowState focusedWindow = getFocusedWindow();
+
+        BufferedWriter out = null;
+
+        // Any uncaught exception will crash the system process
+        try {
+            OutputStream clientStream = client.getOutputStream();
+            out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);
+
+            if(focusedWindow != null) {
+                out.write(Integer.toHexString(System.identityHashCode(focusedWindow)));
+                out.write(' ');
+                out.append(focusedWindow.mAttrs.getTitle());
+            }
+            out.write('\n');
+            out.flush();
+        } catch (Exception e) {
+            result = false;
+        } finally {
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException e) {
+                    result = false;
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
      * Sends a command to a target window. The result of the command, if any, will be
      * written in the output stream of the specified socket.
      *
diff --git a/telephony/java/com/android/internal/telephony/MccTable.java b/telephony/java/com/android/internal/telephony/MccTable.java
index 5dd29af..9b0aa3c 100644
--- a/telephony/java/com/android/internal/telephony/MccTable.java
+++ b/telephony/java/com/android/internal/telephony/MccTable.java
@@ -416,7 +416,7 @@
 		table.add(new MccEntry(438,"tm",2));	//Turkmenistan
 		table.add(new MccEntry(440,"jp",2,"ja",14));	//Japan
 		table.add(new MccEntry(441,"jp",2,"ja",14));	//Japan
-		table.add(new MccEntry(450,"kr",2));	//Korea (Republic of)
+		table.add(new MccEntry(450,"kr",2,"ko",13));	//Korea (Republic of)
 		table.add(new MccEntry(452,"vn",2));	//Viet Nam (Socialist Republic of)
 		table.add(new MccEntry(454,"hk",2));	//"Hong Kong, China"
 		table.add(new MccEntry(455,"mo",2));	//"Macao, China"
diff --git a/test-runner/src/android/test/mock/MockContentProvider.java b/test-runner/src/android/test/mock/MockContentProvider.java
index 3fd71c8..b63ff3d 100644
--- a/test-runner/src/android/test/mock/MockContentProvider.java
+++ b/test-runner/src/android/test/mock/MockContentProvider.java
@@ -127,6 +127,16 @@
             throw new UnsupportedOperationException();
         }
 
+        @SuppressWarnings("unused")
+        public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException {
+            return MockContentProvider.this.getStreamTypes(url, mimeTypeFilter);
+        }
+
+        @SuppressWarnings("unused")
+        public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
+                throws RemoteException, FileNotFoundException {
+            return MockContentProvider.this.openTypedAssetFile(url, mimeType, opts);
+        }
     }
     private final InversionIContentProvider mIContentProvider = new InversionIContentProvider();
 
@@ -222,6 +232,14 @@
         throw new UnsupportedOperationException("unimplemented mock method call");
     }
 
+    public String[] getStreamTypes(Uri url, String mimeTypeFilter) {
+        throw new UnsupportedOperationException("unimplemented mock method call");
+    }
+
+    public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts) {
+        throw new UnsupportedOperationException("unimplemented mock method call");
+    }
+
     /**
      * Returns IContentProvider which calls back same methods in this class.
      * By overriding this class, we avoid the mechanism hidden behind ContentProvider
diff --git a/test-runner/src/android/test/mock/MockIContentProvider.java b/test-runner/src/android/test/mock/MockIContentProvider.java
index 0be5bea..183be41 100644
--- a/test-runner/src/android/test/mock/MockIContentProvider.java
+++ b/test-runner/src/android/test/mock/MockIContentProvider.java
@@ -32,6 +32,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 
+import java.io.FileNotFoundException;
 import java.util.ArrayList;
 
 /**
@@ -102,4 +103,13 @@
     public IBinder asBinder() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
+
+    public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
+            throws RemoteException, FileNotFoundException {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
 }
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas.java b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
index d5d315e..1e1aba9 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
@@ -69,11 +69,6 @@
         throw new UnsupportedOperationException("Can't create Canvas(int)");
     }
 
-    public Canvas(javax.microedition.khronos.opengles.GL gl) {
-        mLogger = null;
-        throw new UnsupportedOperationException("Can't create Canvas(javax.microedition.khronos.opengles.GL)");
-    }
-
     // custom constructors for our use.
     public Canvas(int width, int height, ILayoutLog logger) {
         mLogger = logger;
@@ -1174,15 +1169,6 @@
     }
 
     /* (non-Javadoc)
-     * @see android.graphics.Canvas#getGL()
-     */
-    @Override
-    public GL getGL() {
-        // TODO Auto-generated method stub
-        return super.getGL();
-    }
-
-    /* (non-Javadoc)
      * @see android.graphics.Canvas#getMatrix()
      */
     @Override
@@ -1257,15 +1243,6 @@
     }
 
     /* (non-Javadoc)
-     * @see android.graphics.Canvas#setViewport(int, int)
-     */
-    @Override
-    public void setViewport(int width, int height) {
-        // TODO Auto-generated method stub
-        super.setViewport(width, height);
-    }
-
-    /* (non-Javadoc)
      * @see android.graphics.Canvas#skew(float, float)
      */
     @Override